[
  {
    "path": ".gitignore",
    "content": "node_modules/\n.expo/\ndist/\nnpm-debug.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n*.orig.*\nweb-build/\n\n# macOS\n.DS_Store\n\n# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb\n# The following patterns were generated by expo-cli\n\nexpo-env.d.ts\n# @end expo-cli"
  },
  {
    "path": "README.md",
    "content": "# Apple Music Sheet UI Demo with Expo\n\nThis project demonstrates an implementation of the Apple Music player UI in React Native using Expo, with a focus on replicating the smooth sheet transitions and scaling animations.\n\n![Demo](assets/gifs/demo1.gif)\n\n## Features\n\n- 🎵 Full-screen music player modal with gesture controls\n- 🔄 Smooth scaling animations of the root content\n- 👆 Interactive pan gesture handling\n- 📱 iOS-style sheet presentation\n- 🎨 Dynamic border radius animations\n- 🌟 Visual audio visualizer\n- 💫 Haptic feedback on modal interactions\n- 🖼️ Blur effects and backdrop filters\n- 📱 Sticky mini-player navigation\n- 📋 Apple Music style track listing\n- ⚡ Gesture handling with drag thresholds\n- 🔄 Horizontal swipe to dismiss\n\n## Tech Stack\n\n- [Expo](https://expo.dev) - React Native development platform\n- [Expo Router](https://docs.expo.dev/router/introduction) - File-based routing\n- [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/) - Smooth animations\n- [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/) - Native-driven gesture handling\n\n## Getting Started\n\n1. Install dependencies:\n\n   ```bash\n   npm install\n   ```\n\n2. Start the development server:\n\n   ```bash\n   npx expo start\n   ```\n\n3. Open in iOS Simulator or Android Emulator:\n   - Press `i` for iOS\n   - Press `a` for Android\n\n## Implementation Details\n\nThe project showcases several key features of modern React Native development:\n\n- Shared element transitions between mini and full player\n- Gesture-based interactions with multi-axis support\n- Context-based animation state management\n- Worklet-based animations for optimal performance\n\n### Known Issues\n\n- Horizontal drag gesture conflicts with content scrolling when the modal is partially scrolled, causing flickering. This needs to be addressed by properly managing gesture priorities and scroll state.\n\n## Project Structure\n\n```\nproject-root/\n├── app/\n│   ├── (tabs)/\n│   │   ├── search/            # Search and library screens\n│   │   │   ├── _layout.tsx\n│   │   │   ├── library.tsx\n│   │   │   ├── new.tsx\n│   │   │   └── radio.tsx\n│   │   ├── music/             # Music player routes\n│   │   │   ├── [id].tsx\n│   │   │   └── _layout.tsx\n│   │   └── _layout.tsx        # Tab navigation layout\n├── components/\n│   ├── navigation/\n│   │   └── TabBarIcon.tsx     # Tab bar icons\n│   ├── Overlay/               # Sheet UI components\n│   │   ├── OverlayContext.tsx\n│   │   ├── OverlayProvider.tsx\n│   │   └── ThemedView.tsx\n│   └── ThemedText.tsx\n├── contexts/\n│   ├── AudioContext.tsx       # Audio playback state\n│   └── RootScaleContext.tsx   # Scale animation state\n├── constants/\n│   └── Colors.ts             # Theme colors\n└── hooks/                    # Custom React hooks\n    ├── useColorScheme.ts\n    ├── useThemeColor.ts\n    └── useColorScheme.web.ts\n```\n\n## Contributing\n\nFeel free to contribute to this project by:\n\n1. Forking the repository\n2. Creating a feature branch\n3. Submitting a pull request\n\n## License\n\nThis project is open source and available under the MIT License.\n"
  },
  {
    "path": "app/(tabs)/_layout.tsx",
    "content": "import { Tabs } from 'expo-router';\nimport React from 'react';\nimport { TabBarIcon } from '@/components/navigation/TabBarIcon';\nimport { Colors } from '@/constants/Colors';\nimport { useColorScheme } from '@/hooks/useColorScheme';\nimport { BlurView } from 'expo-blur';\nimport { Platform, StyleSheet } from 'react-native';\nimport { SymbolView } from 'expo-symbols';\n\n// Helper component for cross-platform icons\nfunction TabIcon({ sfSymbol, ionIcon, color }: { sfSymbol: string; ionIcon: string; color: string }) {\n  // if (Platform.OS === 'ios') {\n  //   return (\n  //     <SymbolView\n  //       name={sfSymbol}\n  //       size={24}\n  //       tintColor={color}\n  //       fallback={<TabBarIcon name={ionIcon} color={color} />}\n  //     />\n  //   );\n  // }\n  return <TabBarIcon name={ionIcon} color={color} />;\n}\n\nexport default function TabLayout() {\n  const colorScheme = useColorScheme();\n\n  return (\n    <Tabs\n      screenOptions={{\n        tabBarActiveTintColor: '#FA2D48',\n        headerShown: false,\n        tabBarStyle: {\n          position: 'absolute',\n          backgroundColor: Platform.select({\n            ios: 'transparent',\n            android: 'rgba(255, 255, 255, 0.8)', // Fallback for Android\n          }),\n          borderTopWidth: 0,\n          elevation: 0,\n          height: 94,\n          paddingTop: 0,\n          paddingBottom: 40,\n        },\n        tabBarBackground: () => (\n          Platform.OS === 'ios' ? (\n            <BlurView\n              tint={colorScheme === 'dark' ? 'systemThickMaterialDark' : 'systemThickMaterialLight'}\n              intensity={80}\n              style={StyleSheet.absoluteFill}\n            />\n          ) : null\n        ),\n      }}>\n      <Tabs.Screen\n        name=\"index\"\n        options={{\n          title: 'Home',\n          tabBarIcon: ({ color }) => (\n            <TabIcon\n              sfSymbol=\"music.note.house\"\n              ionIcon=\"home-sharp\"\n              color={color}\n            />\n          ),\n        }}\n      />\n      <Tabs.Screen\n        name=\"new\"\n        options={{\n          title: 'New',\n          tabBarIcon: ({ color }) => (\n            <TabIcon\n              sfSymbol=\"square.grid.2x2.fill\"\n              ionIcon=\"apps-sharp\"\n              color={color}\n            />\n          ),\n        }}\n      />\n      <Tabs.Screen\n        name=\"radio\"\n        options={{\n          title: 'Radio',\n          tabBarIcon: ({ color }) => (\n            <TabIcon\n              sfSymbol=\"dot.radiowaves.left.and.right\"\n              ionIcon=\"radio-outline\"\n              color={color}\n            />\n          ),\n        }}\n      />\n      <Tabs.Screen\n        name=\"library\"\n        options={{\n          title: 'Library',\n          tabBarIcon: ({ color }) => (\n            <TabIcon\n              sfSymbol=\"music.note.list\"\n              ionIcon=\"musical-notes\"\n              color={color}\n            />\n          ),\n        }}\n      />\n      <Tabs.Screen\n        name=\"search\"\n        options={{\n          title: 'Search',\n          tabBarIcon: ({ color }) => (\n            <TabIcon\n              sfSymbol=\"magnifyingglass\"\n              ionIcon=\"search\"\n              color={color}\n            />\n          ),\n        }}\n      />\n    </Tabs>\n  );\n}\n"
  },
  {
    "path": "app/(tabs)/index.tsx",
    "content": "import { Text, Image, View, StyleSheet, Platform, Pressable, FlatList } from 'react-native';\nimport { useRouter } from 'expo-router';\nimport { useState } from 'react';\nimport { MaterialIcons, Ionicons } from '@expo/vector-icons';\n\nimport ParallaxScrollView from '@/components/ParallaxScrollView';\nimport { ThemedText } from '@/components/ThemedText';\nimport { ThemedView } from '@/components/ThemedView';\nimport { songs } from '@/data/songs.json';\nimport { useAudio } from '@/contexts/AudioContext';\nimport { MusicVisualizer } from '@/components/MusicVisualizer';\nimport { useColorScheme } from '@/hooks/useColorScheme';\n\ninterface Song {\n  id: string;\n  title: string;\n  artist: string;\n  artwork: string;\n}\n\nexport default function HomeScreen() {\n  const router = useRouter();\n  const { currentSong, playSound, isPlaying, togglePlayPause } = useAudio();\n  const colorScheme = useColorScheme();\n\n  const handlePlayFirst = () => {\n    playSound(songs[0]);\n  };\n\n  const handleShuffle = () => {\n    const randomSong = songs[Math.floor(Math.random() * songs.length)];\n    playSound(randomSong);\n  };\n\n  const renderSongItem = ({ item }: { item: Song }) => (\n    <Pressable\n      onPress={() => {\n        playSound(item);\n        // router.push(`/music/${item.id}`);\n      }}\n      style={styles.songItem}\n    >\n      <View style={styles.artworkContainer}>\n        <Image source={{ uri: item.artwork }} style={styles.songArtwork} />\n        {item.id === currentSong?.id && (\n          <MusicVisualizer isPlaying={isPlaying} />\n        )}\n      </View>\n\n      <ThemedView\n        style={[\n          styles.songInfoContainer,\n          { borderBottomColor: colorScheme === 'light' ? '#ababab' : '#535353' },\n        ]}\n      >\n        <ThemedView style={styles.songInfo}>\n          <ThemedText type=\"defaultSemiBold\" numberOfLines={1} style={styles.songTitle}>\n            {item.title}\n          </ThemedText>\n          <ThemedView style={styles.artistRow}>\n            {item.id === currentSong?.id && (\n              <Ionicons name=\"musical-note\" size={12} color=\"#FA2D48\" />\n            )}\n            <ThemedText type=\"subtitle\" numberOfLines={1} style={styles.songArtist}>\n              {item.artist}\n            </ThemedText>\n          </ThemedView>\n        </ThemedView>\n        <Pressable style={styles.moreButton}>\n          <MaterialIcons name=\"more-horiz\" size={20} color=\"#222222\" />\n        </Pressable>\n      </ThemedView>\n    </Pressable>\n  );\n\n  return (\n    <ThemedView style={styles.container}>\n      <ParallaxScrollView\n        headerBackgroundColor={{ light: '#f57a8a', dark: '#FA2D48' }}\n        headerImage={\n          <ThemedView style={{\n            flex: 1, width: '100%', height: '100%', position: 'absolute', top: 0, left: 0,\n            alignItems: 'center',\n\n          }}>\n            <Image\n              source={{\n                uri: 'https://9to5mac.com/wp-content/uploads/sites/6/2021/08/apple-music-logo-2021-9to5mac.jpg?quality=82&strip=all&w=1024'\n              }}\n              style={{\n                position: 'absolute',\n                width: '100%',\n                height: '100%'\n              }}\n            />\n            <Text style={{\n              fontSize: 18,\n              letterSpacing: -0.5,\n              alignSelf: 'center',\n              position: 'absolute',\n              top: 80,\n              color: '#fff'  // Added white color for better visibility\n            }}>\n              Built with Expo\n            </Text>\n\n            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', gap: 10 }}>\n              <View style={styles.headerButtons}>\n                <Pressable\n                  style={styles.headerButton}\n                  onPress={handlePlayFirst}\n                >\n                  <Ionicons name=\"play\" size={24} color=\"#fff\" />\n                  <Text style={styles.headerButtonText}>Play</Text>\n                </Pressable>\n                <Pressable\n                  style={styles.headerButton}\n                  onPress={handleShuffle}\n                >\n                  <Ionicons name=\"shuffle\" size={24} color=\"#fff\" />\n                  <Text style={styles.headerButtonText}>Shuffle</Text>\n                </Pressable>\n              </View>\n\n            </View>\n\n          </ThemedView>\n        }\n        contentContainerStyle={styles.scrollView}\n      >\n        <ThemedView style={styles.titleContainer}>\n          <ThemedView style={styles.titleRow}>\n            <ThemedText type=\"title\">Billboard Top 20</ThemedText>\n          </ThemedView>\n          <ThemedText type=\"subtitle\">\n            {new Date().toLocaleDateString('en-US', {\n              month: 'long',\n              day: 'numeric',\n              year: 'numeric'\n            })}\n          </ThemedText>\n        </ThemedView>\n\n        <FlatList\n          data={songs}\n          renderItem={renderSongItem}\n          keyExtractor={item => item.id}\n          scrollEnabled={false}\n        />\n      </ParallaxScrollView>\n    </ThemedView>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n\n  },\n  scrollView: {\n    flex: 1,\n  },\n  titleContainer: {\n    flexDirection: 'column',\n    // marginBottom: 20,\n    paddingHorizontal: 16,\n    paddingVertical: 16\n  },\n  titleRow: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    gap: 8,\n\n  },\n  reactLogo: {\n    height: 50,\n    width: 210,\n    bottom: 0,\n    top: 100,\n  },\n  songItem: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    marginBottom: 6,\n    gap: 12,\n    paddingLeft: 16\n\n  },\n  artworkContainer: {\n    position: 'relative',\n    width: 50,\n    height: 50,\n\n  },\n  songArtwork: {\n    width: '100%',\n    height: '100%',\n    borderRadius: 4,\n  },\n  songInfo: {\n    flex: 1,\n    gap: 4,\n    backgroundColor: 'transparent'\n\n  },\n  artistRow: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    gap: 4,\n    backgroundColor: 'transparent'\n  },\n  songTitle: {\n    fontSize: 15,\n    fontWeight: '400',\n  },\n  songArtist: {\n    fontSize: 14,\n    fontWeight: '400',\n    opacity: 0.6,\n    marginTop: -4\n  },\n  moreButton: {\n    padding: 8,\n  },\n  headerButtons: {\n    flexDirection: 'row',\n    justifyContent: 'center',\n    gap: 20,\n    position: 'absolute',\n    bottom: 30,\n    // width: '100%',\n\n    marginHorizontal: 20,\n  },\n  headerButton: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    backgroundColor: 'rgba(0,0,0,0.1)',\n    paddingHorizontal: 16,\n    paddingVertical: 8,\n    borderRadius: 10,\n    gap: 8,\n    flex: 1,\n    justifyContent: 'center',\n  },\n  headerButtonText: {\n    color: '#fff',\n    fontSize: 16,\n    fontWeight: '600',\n  },\n  songInfoContainer: {\n    flex: 1,\n    gap: 4,\n    flexDirection: 'row',\n    borderBottomWidth: StyleSheet.hairlineWidth,\n    paddingBottom: 14,\n    paddingRight: 14\n  },\n});\n"
  },
  {
    "path": "app/(tabs)/library.tsx",
    "content": "import { View, Text } from 'react-native';\nimport React from 'react';\n\nexport default function LibraryScreen() {\n    return (\n        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>\n            <Text>Library Screen</Text>\n        </View>\n    );\n} "
  },
  {
    "path": "app/(tabs)/new.tsx",
    "content": "import { View, Text } from 'react-native';\nimport React from 'react';\n\nexport default function NewScreen() {\n    return (\n        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>\n            <Text>New Screen</Text>\n        </View>\n    );\n} "
  },
  {
    "path": "app/(tabs)/radio.tsx",
    "content": "import { View, Text } from 'react-native';\nimport React from 'react';\n\nexport default function RadioScreen() {\n    return (\n        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>\n            <Text>Radio Screen</Text>\n        </View>\n    );\n} "
  },
  {
    "path": "app/(tabs)/search/_layout.tsx",
    "content": "import { Stack } from 'expo-router';\nimport { router } from 'expo-router';\n\nexport default function SearchStack() {\n    return (\n        <Stack>\n            <Stack.Screen\n                name=\"index\"\n                options={{\n                    headerLargeTitle: true,\n                    headerLargeTitleStyle: { fontWeight: 'bold' },\n                    title: \"Search\",\n                    headerShadowVisible: false,\n                    headerStyle: {\n\n                    },\n                    headerSearchBarOptions: {\n                        placeholder: \"Artists, Songs, Lyrics, and More\",\n                        onFocus: () => {\n                            router.push(\"/search/search\" as any);\n                        },\n                    }\n                }}\n            />\n        </Stack>\n    );\n}"
  },
  {
    "path": "app/(tabs)/search/index.tsx",
    "content": "import { View, Text, ScrollView, TextInput, StyleSheet } from 'react-native';\nimport React from 'react';\nimport { CategoryCard } from '@/components/CategoryCard';\nimport { Ionicons } from '@expo/vector-icons';\nimport { ThemedText } from '@/components/ThemedText';\n\nconst categories = [\n    {\n        \"artworkBgColor\": \"#031312\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features114/v4/71/cb/37/71cb3751-1b55-41ae-9993-68f0682189fc/U0gtTVMtV1ctSGFsbG93ZWVuLU92ZXJhcmNoaW5nLnBuZw.png/1040x586sr.webp\",\n        \"title\": \"Halloween\"\n    },\n    {\n        \"artworkBgColor\": \"#f83046\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/cc/54/da/cc54dac4-4972-69d2-9a5b-462d2d1ee8a6/c940e51b-6644-4b17-a714-1c898f669fb5.png/1040x586sr.webp\",\n        \"title\": \"Spatial Audio\"\n    },\n    {\n        \"artworkBgColor\": \"#6b8ce9\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/fc/16/77/fc16775e-522b-d5c7-16ea-f5a7947ba9e2/1a7e6d73-b9c0-4ed2-a6a2-942900d1ce26.png/1040x586sr.webp\",\n        \"title\": \"Hip-Hop\"\n    },\n    {\n        \"artworkBgColor\": \"#d18937\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/08/37/a9/0837a924-1d3a-7987-b179-b61c14222e5e/d27004f3-3a1f-4d0c-ae9a-d12425eec39e.png/1040x586sr.webp\",\n        \"title\": \"Country\"\n    },\n    {\n        \"artworkBgColor\": \"#e46689\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/1d/43/86/1d438675-0ec5-b7c3-84aa-ab011159b921/db3b6833-715a-4119-9706-f8c51b6cf0c0.png/1040x586sr.webp\",\n        \"title\": \"Pop\"\n    },\n    {\n        \"artworkBgColor\": \"#ebbada\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/e3/01/4e/e3014ee5-004a-f936-03a2-f8b4f4904666/64857193-8605-490a-9699-04f4e6638719.png/1040x586sr.webp\",\n        \"title\": \"Apple Music Live\"\n    },\n    {\n        \"artworkBgColor\": \"#4e246e\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/66/44/63/6644636a-6134-e464-32e6-a7900d583ce8/bcb74429-1303-4fb0-9c3f-a6f0b87eb86e.png/1040x586sr.webp\",\n        \"title\": \"Sleep\"\n    },\n    {\n        \"artworkBgColor\": \"#595920\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/4c/59/c8/4c59c8c1-d144-dcef-b895-09204781995a/2362fc03-e10d-4b56-aca1-015246a9229d.png/1040x586sr.webp\",\n        \"title\": \"Charts\"\n    },\n    {\n        \"artworkBgColor\": \"#28585e\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/0f/17/d5/0f17d5a3-6774-1ae1-4530-2b694d8fb6bf/d7944211-2928-4ccc-b382-f0564bcf00b2.png/1040x586sr.webp\",\n        \"title\": \"Chill\"\n    },\n    {\n        \"artworkBgColor\": \"#8e75d8\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/58/1d/ff/581dfff1-b631-4302-fba7-8e2364ace98d/f2bec4ab-0d3c-46e1-98dd-a0a1588dae30.png/1040x586sr.webp\",\n        \"title\": \"R&B\"\n    },\n    {\n        \"artworkBgColor\": \"#dd4a82\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features/v4/08/a3/d4/08a3d409-a1d4-8d3d-bd44-92f6b211a7c3/2b0a4241-2168-43a7-8ae6-5e54696e5ec2.png/1040x586sr.webp\",\n        \"title\": \"Latin\"\n    },\n    {\n        \"artworkBgColor\": \"#3cbb7d\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/e9/40/4b/e9404be8-f887-2573-06e1-7a259af3c6a9/1c1dc89d-8fe5-4554-b2f0-963f375b58c0.png/1040x586sr.webp\",\n        \"title\": \"Dance\"\n    },\n    {\n        \"artworkBgColor\": \"#fa3348\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/12/de/da/12deda1c-4c53-3bef-91d7-c1c7ec3725f2/a0e305b8-db6a-4c6e-819a-d45936097194.png/1040x586sr.webp\",\n        \"title\": \"DJ Mixes\"\n    },\n    {\n        \"artworkBgColor\": \"#ddb71e\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features/v4/ea/c3/b1/eac3b150-ad9e-1086-4284-b4e6b43757d7/2520a131-0639-423c-b167-9b73931e5cb0.png/1040x586sr.webp\",\n        \"title\": \"Hits\"\n    },\n    {\n        \"artworkBgColor\": \"#808a20\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/20/fe/98/20fe9879-9f3c-67d7-9a29-c26ea4624498/a023fa37-e402-416b-a034-47dbc3c0003f.png/1040x586sr.webp\",\n        \"title\": \"Fitness\"\n    },\n    {\n        \"artworkBgColor\": \"#70441b\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/30/e6/42/30e64278-008f-b3df-6790-bdd7fe382360/f3639799-0252-4a92-b3d4-3df02f586e29.png/1040x586sr.webp\",\n        \"title\": \"Feel Good\"\n    },\n    {\n        \"artworkBgColor\": \"#9f3873\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/13/12/7d/13127dc4-9c81-099c-31a7-afb849518840/06e568d2-c588-448f-8721-1739e6ac2f2f.png/1040x586sr.webp\",\n        \"title\": \"Party\"\n    },\n    {\n        \"artworkBgColor\": \"#c4bc1e\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/34/7a/f4/347af4f3-a90b-e6a6-0244-f03ae159cf21/27107b4f-fb04-43d9-beed-8f315445fce9.png/1040x586sr.webp\",\n        \"title\": \"Alternative\"\n    },\n    {\n        \"artworkBgColor\": \"#db6646\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/8a/b7/2d/8ab72d43-2134-f98f-84a8-301c5653d07f/4b68c547-2be2-4bdd-974b-36d1a3e1bf3a.png/1040x586sr.webp\",\n        \"title\": \"Rock\"\n    },\n    {\n        \"artworkBgColor\": \"#da5c39\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features126/v4/03/53/b9/0353b95e-089e-63d9-5f1a-e07dbe85ae14/62e5b81d-fef3-46d0-b33b-537d1e85f24f.png/1040x586sr.webp\",\n        \"title\": \"Classic Rock\"\n    },\n    {\n        \"artworkBgColor\": \"#744808\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/32/52/4d/32524d36-af28-8bfe-ac7c-66494ee10362/8540fcf4-af4a-4943-af34-1355797f90e1.png/1040x586sr.webp\",\n        \"title\": \"Focus\"\n    },\n    {\n        \"artworkBgColor\": \"#351433\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/e1/3c/80/e13c8044-bd0b-bb30-61b0-439123c83af7/6fd5f161-9e42-42ee-9257-f17dd321d34f.png/1040x586sr.webp\",\n        \"title\": \"Essentials\"\n    },\n    {\n        \"artworkBgColor\": \"#2caaaf\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/ef/32/2c/ef322c4f-9ee2-b57e-ce35-d0c3adc2dce5/1b3cfebe-2ff7-49d3-bf4c-1549729faf06.png/1040x586sr.webp\",\n        \"title\": \"Christian\"\n    },\n    {\n        \"artworkBgColor\": \"#63377b\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features116/v4/e2/28/4f/e2284f11-e4a5-ce12-3281-6cfa993928e5/329b7acb-044a-414a-8227-78750bdfb511.png/1040x586sr.webp\",\n        \"title\": \"Classical\"\n    },\n    {\n        \"artworkBgColor\": \"#c48820\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/f5/6e/46/f56e4626-b04b-b8e2-3c31-42d3e671df9c/b9972b6d-009d-4d6e-81f6-5254a77eea89.png/1040x586sr.webp\",\n        \"title\": \"Música Mexicana\"\n    },\n    {\n        \"artworkBgColor\": \"#da5c39\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features/v4/0f/1f/cd/0f1fcd6d-a660-259f-d2c4-319761070011/861cb0eb-168a-46cd-8585-c2e3f316f55b.png/1040x586sr.webp\",\n        \"title\": \"Hard Rock\"\n    },\n    {\n        \"artworkBgColor\": \"#af2d5f\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/d0/b6/dc/d0b6dc3a-8063-f2a3-152f-afd8c36b22b9/2d56098f-42f6-41fb-92dd-13bb5a2ee7db.png/1040x586sr.webp\",\n        \"title\": \"Urbano Latino\"\n    },\n    {\n        \"artworkBgColor\": \"#e46689\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/a2/da/d8/a2dad825-c24c-6cf6-0266-a8f4ac6c2ef8/daf15085-ea0a-4665-ac79-c9e2e94dfe7d.png/1040x586sr.webp\",\n        \"title\": \"K-Pop\"\n    },\n    {\n        \"artworkBgColor\": \"#58b556\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/bb/0e/29/bb0e29d9-7645-7ab0-f2c9-540c2f4eec2f/ca0818f2-a6db-4f9d-a1ae-3fef4d9ea10d.png/1040x586sr.webp\",\n        \"title\": \"Kids\"\n    },\n    {\n        \"artworkBgColor\": \"#4aaf49\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features/v4/ac/1b/f8/ac1bf8a7-c4fb-a1b0-90aa-3c9a7afd5f28/aadcdfed-207b-4961-9884-a37a64eb9bcd.png/1040x586sr.webp\",\n        \"title\": \"Family\"\n    },\n    {\n        \"artworkBgColor\": \"#21265c\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features126/v4/c9/ae/47/c9ae4709-c461-f729-23ef-2c68ad37341e/e5bf281e-54e4-46dc-b9a2-ca961cc99f81.png/1040x586sr.webp\",\n        \"title\": \"Music Videos\"\n    },\n    {\n        \"artworkBgColor\": \"#f83046\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/4f/db/a1/4fdba177-3af6-6bb7-2f43-7938e1f227a6/74e5adc8-240b-40e6-ad09-c54f426283bd.png/1040x586sr.webp\",\n        \"title\": \"Up Next\"\n    },\n    {\n        \"artworkBgColor\": \"#755323\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/56/76/7d/56767d51-89b9-e6ce-2e7b-c8c3cf954f47/e23544e8-6aad-45e7-b555-5695fd5f883a.png/1040x586sr.webp\",\n        \"title\": \"Decades\"\n    },\n    {\n        \"artworkBgColor\": \"#e25a80\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features/v4/75/6e/5e/756e5e72-22bc-57de-7f2b-c6d042417879/00eb9147-24ac-4223-842e-519e19f36f7f.png/1040x586sr.webp\",\n        \"title\": \"Pop Latino\"\n    },\n    {\n        \"artworkBgColor\": \"#ae2b29\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features126/v4/b5/c6/d2/b5c6d2ad-0e69-1d37-d7a0-3699d4f4909b/2b419129-d471-44fd-bbfa-2d36d07a6787.png/1040x586sr.webp\",\n        \"title\": \"Metal\"\n    },\n    {\n        \"artworkBgColor\": \"#dab10d\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features126/v4/e9/73/69/e97369b3-c68c-1047-6086-08ae0c747469/9d4f61ac-4d2f-4546-98b3-4db3b1ccb01b.png/1040x586sr.webp\",\n        \"title\": \"2000s\"\n    },\n    {\n        \"artworkBgColor\": \"#34b799\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/72/0f/7b/720f7bc9-97dd-9665-8394-aee9f279fa2a/9222ca98-8663-4ae9-889b-79eb10fe5acb.png/1040x586sr.webp\",\n        \"title\": \"Indie\"\n    },\n    {\n        \"artworkBgColor\": \"#3cbb7d\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/63/a2/0b/63a20b1e-f138-8ba9-5c1f-b1533907ce42/ed74c8f0-2343-4c38-b3e3-e29cb75afbe4.png/1040x586sr.webp\",\n        \"title\": \"Electronic\"\n    },\n    {\n        \"artworkBgColor\": \"#363110\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/ad/69/1c/ad691cf0-f626-6050-3361-28e0995849bf/f964cbb9-d787-4faf-97b8-73958872c4c1.png/1040x586sr.webp\",\n        \"title\": \"Behind the Songs\"\n    },\n    {\n        \"artworkBgColor\": \"#29a5c9\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/64/34/e6/6434e622-46be-5e71-74ba-44b74e98c3a3/a16e4f0f-9eed-4f8d-8d14-b960760dccf5.png/1040x586sr.webp\",\n        \"title\": \"Jazz\"\n    },\n    {\n        \"artworkBgColor\": \"#7ab539\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features211/v4/5b/90/cd/5b90cd44-2a7d-39aa-2992-f3af5b9f98b1/335ccbc3-fcdd-4082-8367-fe8df5ffd9ad.png/1040x586sr.webp\",\n        \"title\": \"Reggae\"\n    },\n    {\n        \"artworkBgColor\": \"#0a070f\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features116/v4/d0/69/d3/d069d3d0-b4ea-07a3-a95c-7f025adf8f60/4e941745-d668-49d1-90c3-c2a53340097f.png/1040x586sr.webp\",\n        \"title\": \"Film, TV & Stage\"\n    },\n    {\n        \"artworkBgColor\": \"#881834\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/01/42/38/0142387d-608e-45d2-c839-fb534d5d4259/1e691432-e96a-4042-aea5-79c6d0bbb9af.png/1040x586sr.webp\",\n        \"title\": \"Motivation\"\n    },\n    {\n        \"artworkBgColor\": \"#8280e7\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/46/52/f7/4652f73c-1785-4273-3282-2c4b116597d6/9a4c5c53-a845-48b2-a1a6-762f4e688df8.png/1040x586sr.webp\",\n        \"title\": \"Soul/Funk\"\n    },\n    {\n        \"artworkBgColor\": \"#53b69e\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/e2/e4/ef/e2e4ef7e-2611-91a2-bd17-faefa1ac23a7/009cfa78-893d-4e6a-8c8d-2592de8b83f7.png/1040x586sr.webp\",\n        \"title\": \"Wellbeing\"\n    },\n    {\n        \"artworkBgColor\": \"#dab10d\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features116/v4/17/9d/5e/179d5e31-c15a-08f8-78eb-a7e83ecbf2f0/d676ae15-36eb-4748-9a67-1bdb5ba8efaa.png/1040x586sr.webp\",\n        \"title\": \"2010s\"\n    },\n    {\n        \"artworkBgColor\": \"#dab10d\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features126/v4/73/c5/16/73c51608-bb46-c3f4-ee8f-44f534829ca4/8d7abe21-9bc8-49bc-b8ff-8e948ec081b3.png/1040x586sr.webp\",\n        \"title\": \"’60s\"\n    },\n    {\n        \"artworkBgColor\": \"#cd8028\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features116/v4/b1/86/89/b1868989-f8c4-2e7a-b3ea-317ae3b72aa1/6f017db2-efe7-4c64-bddf-2d909e2f4ae2.png/1040x586sr.webp\",\n        \"title\": \"Americana\"\n    },\n    {\n        \"artworkBgColor\": \"#4997d5\",\n        \"artworkImage\": \"https://is1-ssl.mzstatic.com/image/thumb/Features221/v4/5d/f0/0d/5df00dca-a4b7-2da5-026c-84dd8520de75/14a698b9-248f-4ea9-8f02-1a341c401735.png/1040x586sr.webp\",\n        \"title\": \"Blues\"\n    }\n]\n\nexport default function SearchScreen() {\n    return (\n        <ScrollView\n            style={styles.container}\n            contentInsetAdjustmentBehavior=\"automatic\"\n        >\n            <ThemedText style={styles.title}>Browse Categories</ThemedText>\n            <View style={styles.categoriesContainer}>\n                {categories.map((category, index) => (\n                    <View key={index} style={styles.categoryWrapper}>\n                        <CategoryCard\n                            title={category.title}\n                            backgroundColor={category.artworkBgColor}\n                            imageUrl={category.artworkImage}\n                        />\n                    </View>\n                ))}\n            </View>\n        </ScrollView>\n    );\n}\n\nconst styles = StyleSheet.create({\n    title: {\n        fontSize: 24,\n        fontWeight: 'bold',\n        marginHorizontal: 16,\n        marginTop: 16,\n    },\n    container: {\n        flex: 1,\n        // backgroundColor: '#fff',\n    },\n    categoriesContainer: {\n        flexDirection: 'row',\n        flexWrap: 'wrap',\n        gap: 12,\n        padding: 16,\n    },\n    categoryWrapper: {\n        width: '48%',\n    },\n}); "
  },
  {
    "path": "app/+html.tsx",
    "content": "import { ScrollViewStyleReset } from 'expo-router/html';\nimport { type PropsWithChildren } from 'react';\n\n/**\n * This file is web-only and used to configure the root HTML for every web page during static rendering.\n * The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs.\n */\nexport default function Root({ children }: PropsWithChildren) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta httpEquiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\" />\n\n        {/*\n          Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.\n          However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.\n        */}\n        <ScrollViewStyleReset />\n\n        {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}\n        <style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />\n        {/* Add any additional <head> elements that you want globally available on web... */}\n      </head>\n      <body>{children}</body>\n    </html>\n  );\n}\n\nconst responsiveBackground = `\nbody {\n  background-color: #fff;\n}\n@media (prefers-color-scheme: dark) {\n  body {\n    background-color: #000;\n  }\n}`;\n"
  },
  {
    "path": "app/+not-found.tsx",
    "content": "import { Link, Stack } from 'expo-router';\nimport { StyleSheet } from 'react-native';\n\nimport { ThemedText } from '@/components/ThemedText';\nimport { ThemedView } from '@/components/ThemedView';\n\nexport default function NotFoundScreen() {\n  return (\n    <>\n      <Stack.Screen options={{ title: 'Oops!' }} />\n      <ThemedView style={styles.container}>\n        <ThemedText type=\"title\">This screen doesn't exist.</ThemedText>\n        <Link href=\"/\" style={styles.link}>\n          <ThemedText type=\"link\">Go to home screen!</ThemedText>\n        </Link>\n      </ThemedView>\n    </>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    alignItems: 'center',\n    justifyContent: 'center',\n    padding: 20,\n  },\n  link: {\n    marginTop: 15,\n    paddingVertical: 15,\n  },\n});\n"
  },
  {
    "path": "app/_layout.tsx",
    "content": "import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';\nimport { Stack } from 'expo-router';\nimport * as SplashScreen from 'expo-splash-screen';\nimport { useEffect } from 'react';\nimport { StyleSheet, useColorScheme, View } from 'react-native';\nimport { RootScaleProvider } from '@/contexts/RootScaleContext';\nimport { useRootScale } from '@/contexts/RootScaleContext';\nimport Animated, { useAnimatedStyle } from 'react-native-reanimated';\nimport { GestureHandlerRootView } from 'react-native-gesture-handler';\nimport { OverlayProvider } from '@/components/Overlay/OverlayProvider';\nimport { AudioProvider } from '@/contexts/AudioContext';\nimport { MiniPlayer } from '@/components/BottomSheet/MiniPlayer';\nimport { useRouter } from 'expo-router';\nimport { useAudio } from '@/contexts/AudioContext';\n\nfunction AnimatedStack() {\n  const { scale } = useRootScale();\n  const router = useRouter();\n  const { currentSong, isPlaying, togglePlayPause } = useAudio();\n\n  const animatedStyle = useAnimatedStyle(() => {\n    return {\n      transform: [\n        { scale: scale.value },\n        {\n          translateY: (1 - scale.value) * -150,\n        },\n      ],\n    };\n  });\n\n  return (\n    <View style={{ flex: 1 }}>\n      <Animated.View style={[styles.stackContainer, animatedStyle]}>\n        <Stack>\n          <Stack.Screen name=\"(tabs)\" options={{ headerShown: false }} />\n          <Stack.Screen\n            name=\"music/[id]\"\n            options={{\n              presentation: 'transparentModal',\n              headerShown: false,\n              contentStyle: {\n                backgroundColor: 'transparent',\n              },\n            }}\n          />\n          <Stack.Screen name=\"+not-found\" />\n        </Stack>\n\n\n        {currentSong && (\n          <MiniPlayer\n            song={currentSong}\n            isPlaying={isPlaying}\n            onPlayPause={togglePlayPause}\n            onPress={() => router.push(`/music/${currentSong.id}`)}\n          />\n        )}\n\n\n      </Animated.View>\n\n      {/* putting anything here is not scalled down upon modal open */}\n    </View>\n  );\n}\n\nexport default function RootLayout() {\n  const colorScheme = useColorScheme();\n\n  useEffect(() => {\n    SplashScreen.hideAsync();\n  }, []);\n\n  return (\n    <GestureHandlerRootView style={styles.container}>\n      <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>\n        <RootScaleProvider>\n          <AudioProvider>\n            <OverlayProvider>\n              <AnimatedStack />\n            </OverlayProvider>\n          </AudioProvider>\n        </RootScaleProvider>\n      </ThemeProvider>\n    </GestureHandlerRootView>\n  );\n}\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: '#000',\n  },\n  stackContainer: {\n    flex: 1,\n    overflow: 'hidden',\n    borderRadius: 50,\n  },\n});\n\n"
  },
  {
    "path": "app/music/[id].tsx",
    "content": "import { useLocalSearchParams, useRouter } from 'expo-router';\nimport { StyleSheet, Dimensions } from 'react-native';\nimport { useEffect, useCallback, useRef } from 'react';\nimport { StatusBar } from 'expo-status-bar';\nimport { ThemedView } from '@/components/ThemedView';\nimport { ExpandedPlayer } from '@/components/BottomSheet/ExpandedPlayer';\nimport { useRootScale } from '@/contexts/RootScaleContext';\nimport Animated, {\n    useSharedValue,\n    useAnimatedStyle,\n    withSpring,\n    withTiming,\n    runOnJS,\n} from 'react-native-reanimated';\nimport { GestureDetector, Gesture } from 'react-native-gesture-handler';\nimport { songs } from '@/data/songs.json';\nimport * as Haptics from 'expo-haptics';\n\nconst SCALE_FACTOR = 0.83;\nconst DRAG_THRESHOLD = Math.min(Dimensions.get('window').height * 0.20, 150);\nconst HORIZONTAL_DRAG_THRESHOLD = Math.min(Dimensions.get('window').width * 0.51, 80);\nconst DIRECTION_LOCK_ANGLE = 45; // Angle in degrees to determine horizontal vs vertical movement\nconst ENABLE_HORIZONTAL_DRAG_CLOSE = false;\n\nexport default function MusicScreen() {\n    const { id } = useLocalSearchParams();\n    const router = useRouter();\n    const { setScale } = useRootScale();\n    const translateY = useSharedValue(0);\n    const isClosing = useRef(false);\n    const statusBarStyle = useSharedValue<'light' | 'dark'>('light');\n    const scrollOffset = useSharedValue(0);\n    const isDragging = useSharedValue(false);\n    const translateX = useSharedValue(0);\n    const initialGestureX = useSharedValue(0);\n    const initialGestureY = useSharedValue(0);\n    const isHorizontalGesture = useSharedValue(false);\n    const isScrolling = useSharedValue(false);\n\n    const numericId = typeof id === 'string' ? parseInt(id, 10) : Array.isArray(id) ? parseInt(id[0], 10) : 0;\n    const song = songs.find(s => s.id === numericId) || songs[0];\n\n    const handleHapticFeedback = useCallback(() => {\n        try {\n            Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);\n        } catch (error) {\n            console.log('Haptics not available:', error);\n        }\n    }, []);\n\n    const goBack = useCallback(() => {\n        if (!isClosing.current) {\n            isClosing.current = true;\n            handleHapticFeedback();\n            requestAnimationFrame(() => {\n                router.back();\n            });\n        }\n    }, [router, handleHapticFeedback]);\n\n    const handleScale = useCallback((newScale: number) => {\n        try {\n            setScale(newScale);\n        } catch (error) {\n            console.log('Scale error:', error);\n        }\n    }, [setScale]);\n\n    const calculateGestureAngle = (x: number, y: number) => {\n        'worklet';\n        const angle = Math.abs(Math.atan2(y, x) * (180 / Math.PI));\n        return angle;\n    };\n\n    const panGesture = Gesture.Pan()\n        .onStart((event) => {\n            'worklet';\n            initialGestureX.value = event.x;\n            initialGestureY.value = event.y;\n            isHorizontalGesture.value = false;\n\n            if (scrollOffset.value <= 0) {\n                isDragging.value = true;\n                translateY.value = 0;\n            }\n        })\n        .onUpdate((event) => {\n            'worklet';\n            const dx = event.translationX;\n            const dy = event.translationY;\n            const angle = calculateGestureAngle(dx, dy);\n\n            // Only check for horizontal gesture if enabled\n            if (ENABLE_HORIZONTAL_DRAG_CLOSE && !isHorizontalGesture.value && !isScrolling.value) {\n                if (Math.abs(dx) > 10) {\n                    if (angle < DIRECTION_LOCK_ANGLE) {\n                        isHorizontalGesture.value = true;\n                    }\n                }\n            }\n\n            // Handle horizontal gesture only if enabled\n            if (ENABLE_HORIZONTAL_DRAG_CLOSE && isHorizontalGesture.value) {\n                translateX.value = dx;\n                translateY.value = dy;\n\n                const totalDistance = Math.sqrt(dx * dx + dy * dy);\n                const progress = Math.min(totalDistance / 300, 1);\n\n                const newScale = SCALE_FACTOR + (progress * (1 - SCALE_FACTOR));\n                runOnJS(handleScale)(newScale);\n\n                if (progress > 0.2) {\n                    statusBarStyle.value = 'dark';\n                } else {\n                    statusBarStyle.value = 'light';\n                }\n            }\n            // Handle vertical-only gesture\n            else if (scrollOffset.value <= 0 && isDragging.value) {\n                translateY.value = Math.max(0, dy);\n                const progress = Math.min(dy / 600, 1);\n                const newScale = SCALE_FACTOR + (progress * (1 - SCALE_FACTOR));\n                runOnJS(handleScale)(newScale);\n\n                if (progress > 0.5) {\n                    statusBarStyle.value = 'dark';\n                } else {\n                    statusBarStyle.value = 'light';\n                }\n            }\n        })\n        .onEnd((event) => {\n            'worklet';\n            isDragging.value = false;\n\n            // Handle horizontal gesture end only if enabled\n            if (ENABLE_HORIZONTAL_DRAG_CLOSE && isHorizontalGesture.value) {\n                const dx = event.translationX;\n                const dy = event.translationY;\n                const totalDistance = Math.sqrt(dx * dx + dy * dy);\n                const shouldClose = totalDistance > HORIZONTAL_DRAG_THRESHOLD;\n\n                if (shouldClose) {\n                    // Calculate the exit direction based on the gesture\n                    const exitX = dx * 2;\n                    const exitY = dy * 2;\n\n                    translateX.value = withTiming(exitX, { duration: 300 });\n                    translateY.value = withTiming(exitY, { duration: 300 });\n\n                    runOnJS(handleScale)(1);\n                    runOnJS(handleHapticFeedback)();\n                    runOnJS(goBack)();\n                } else {\n                    // Spring back to original position\n                    translateX.value = withSpring(0, {\n                        damping: 15,\n                        stiffness: 150,\n                    });\n                    translateY.value = withSpring(0, {\n                        damping: 15,\n                        stiffness: 150,\n                    });\n                    runOnJS(handleScale)(SCALE_FACTOR);\n                }\n            }\n            // Handle vertical gesture end\n            else if (scrollOffset.value <= 0) {\n                const shouldClose = event.translationY > DRAG_THRESHOLD;\n\n                if (shouldClose) {\n                    translateY.value = withTiming(event.translationY + 100, {\n                        duration: 300,\n                    });\n                    runOnJS(handleScale)(1);\n                    runOnJS(handleHapticFeedback)();\n                    runOnJS(goBack)();\n                } else {\n                    translateY.value = withSpring(0, {\n                        damping: 15,\n                        stiffness: 150,\n                    });\n                    runOnJS(handleScale)(SCALE_FACTOR);\n                }\n            }\n        })\n        .onFinalize(() => {\n            'worklet';\n            isDragging.value = false;\n            isHorizontalGesture.value = false;\n        });\n\n    const scrollGesture = Gesture.Native()\n        .onBegin(() => {\n            'worklet';\n            isScrolling.value = true;\n            if (!isDragging.value) {\n                translateY.value = 0;\n            }\n        })\n        .onEnd(() => {\n            'worklet';\n            isScrolling.value = false;\n        });\n\n    const composedGestures = Gesture.Simultaneous(panGesture, scrollGesture);\n\n    const ScrollComponent = useCallback((props: any) => {\n        return (\n            <GestureDetector gesture={composedGestures}>\n                <Animated.ScrollView\n                    {...props}\n                    onScroll={(event) => {\n                        'worklet';\n                        scrollOffset.value = event.nativeEvent.contentOffset.y;\n                        if (!isDragging.value && translateY.value !== 0) {\n                            translateY.value = 0;\n                        }\n                        props.onScroll?.(event);\n                    }}\n                    scrollEventThrottle={16}\n                    // bounces={scrollOffset.value >= 0 && !isDragging.value}\n                    bounces={false}\n\n\n                />\n            </GestureDetector>\n        );\n    }, [composedGestures]);\n\n    const animatedStyle = useAnimatedStyle(() => ({\n        transform: [\n            { translateY: translateY.value },\n            { translateX: translateX.value }\n        ],\n        opacity: withSpring(1),\n    }));\n\n    useEffect(() => {\n        const timeout = setTimeout(() => {\n            try {\n                setScale(SCALE_FACTOR);\n            } catch (error) {\n                console.log('Initial scale error:', error);\n            }\n        }, 0);\n\n        return () => {\n            clearTimeout(timeout);\n            try {\n                setScale(1);\n            } catch (error) {\n                console.log('Cleanup scale error:', error);\n            }\n        };\n    }, []);\n\n    return (\n        <ThemedView style={styles.container}>\n            <StatusBar animated={true} style={statusBarStyle.value} />\n            <Animated.View style={[styles.modalContent, animatedStyle]}>\n                <ExpandedPlayer scrollComponent={ScrollComponent} />\n            </Animated.View>\n        </ThemedView>\n    );\n}\n\nconst styles = StyleSheet.create({\n    container: {\n        flex: 1,\n        backgroundColor: 'transparent',\n    },\n    modalContent: {\n        flex: 1,\n        backgroundColor: 'transparent',\n    },\n});\n"
  },
  {
    "path": "app.json",
    "content": "{\n  \"expo\": {\n    \"name\": \"apple-music-sheet-ui\",\n    \"slug\": \"apple-music-sheet-ui\",\n    \"version\": \"1.0.0\",\n    \"orientation\": \"portrait\",\n    \"icon\": \"./assets/images/icon.png\",\n    \"scheme\": \"myapp\",\n    \"userInterfaceStyle\": \"automatic\",\n    \"splash\": {\n      \"image\": \"./assets/images/splash.png\",\n      \"resizeMode\": \"contain\",\n      \"backgroundColor\": \"#ffffff\"\n    },\n    \"ios\": {\n      \"supportsTablet\": true\n    },\n    \"android\": {\n      \"adaptiveIcon\": {\n        \"foregroundImage\": \"./assets/images/adaptive-icon.png\",\n        \"backgroundColor\": \"#ffffff\"\n      }\n    },\n    \"web\": {\n      \"bundler\": \"metro\",\n      \"output\": \"static\",\n      \"favicon\": \"./assets/images/favicon.png\"\n    },\n    \"plugins\": [\"expo-router\"],\n    \"experiments\": {\n      \"typedRoutes\": true\n    }\n  }\n}\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = function (api) {\n  api.cache(true);\n  return {\n    presets: ['babel-preset-expo'],\n  };\n};\n"
  },
  {
    "path": "components/BottomSheet/ExpandedPlayer.tsx",
    "content": "import { View as ThemedView, StyleSheet, Image, Pressable, Dimensions, ScrollView } from 'react-native';\nimport { StatusBar } from 'expo-status-bar';\nimport { ThemedText } from '@/components/ThemedText';\n// import { ThemedView } from '@/components/ThemedView';\nimport { LinearGradient } from 'expo-linear-gradient';\nimport { Ionicons } from '@expo/vector-icons';\nimport { Audio } from 'expo-av';\nimport { useEffect, useState, useCallback } from 'react';\nimport { useAudio } from '@/contexts/AudioContext';\nconst { width } = Dimensions.get('window');\nimport {\n    useSafeAreaInsets,\n} from 'react-native-safe-area-context';\n\nfunction shadeColor(color: string, percent: number): string {\n    const R = parseInt(color.substring(1, 3), 16);\n    const G = parseInt(color.substring(3, 5), 16);\n    const B = parseInt(color.substring(5, 7), 16);\n\n    let newR = Math.round((R * (100 + percent)) / 100);\n    let newG = Math.round((G * (100 + percent)) / 100);\n    let newB = Math.round((B * (100 + percent)) / 100);\n\n    newR = newR < 255 ? newR : 255;\n    newG = newG < 255 ? newG : 255;\n    newB = newB < 255 ? newB : 255;\n\n    const RR = ((newR.toString(16).length === 1) ? \"0\" + newR.toString(16) : newR.toString(16));\n    const GG = ((newG.toString(16).length === 1) ? \"0\" + newG.toString(16) : newG.toString(16));\n    const BB = ((newB.toString(16).length === 1) ? \"0\" + newB.toString(16) : newB.toString(16));\n\n    return \"#\" + RR + GG + BB;\n}\n\ninterface ExpandedPlayerProps {\n    scrollComponent?: (props: any) => React.ReactElement;\n}\n\nexport function ExpandedPlayer({ scrollComponent }: ExpandedPlayerProps) {\n    const ScrollComponentToUse = scrollComponent || ScrollView;\n\n    const {\n        isPlaying,\n        position,\n        duration,\n        togglePlayPause,\n        sound,\n        currentSong,\n        playNextSong,\n        playPreviousSong\n    } = useAudio();\n    const insets = useSafeAreaInsets();\n\n    const colorToUse = currentSong?.artwork_bg_color || \"#000000\";\n    const colors = [colorToUse, shadeColor(colorToUse, -50)];\n\n    const handleSkipForward = async () => {\n        if (sound) {\n            await sound.setPositionAsync(Math.min(duration, position + 10000));\n        }\n    };\n\n    const handleSkipBackward = async () => {\n        if (sound) {\n            await sound.setPositionAsync(Math.max(0, position - 10000));\n        }\n    };\n\n    const formatTime = (millis: number) => {\n        const minutes = Math.floor(millis / 60000);\n        const seconds = ((millis % 60000) / 1000).toFixed(0);\n        return `${minutes}:${Number(seconds) < 10 ? '0' : ''}${seconds}`;\n    };\n\n    const progress = duration > 0 ? (position / duration) * 100 : 0;\n\n    // Add sample lyrics (you should get this from your song data)\n    const lyrics = [\n        \"Verse 1\",\n        \"First line of the song\",\n        \"Second line of the song\",\n        \"Third line goes here\",\n        \"\",\n        \"Chorus\",\n        \"This is the chorus\",\n        \"Another chorus line\",\n        \"Final chorus line\",\n        \"\",\n        \"Verse 2\",\n        \"Back to the verses\",\n        \"More lyrics here\",\n        \"And here as well\",\n        // Add more lyrics as needed\n    ];\n\n    return (\n        <LinearGradient\n            colors={colors}\n            style={[styles.rootContainer, { paddingTop: insets.top }]}\n            start={{ x: 0, y: 0 }}\n            end={{ x: 1, y: 0 }}\n        >\n            <ThemedView style={styles.dragHandleContainer}>\n                <ThemedView style={styles.dragHandle} />\n            </ThemedView>\n\n            <ScrollComponentToUse\n                style={styles.scrollView}\n                showsVerticalScrollIndicator={false}\n            >\n                <ThemedView style={styles.container}>\n                    <ThemedView style={styles.artworkContainer}>\n                        <Image\n                            source={{ uri: currentSong?.artwork }}\n                            style={styles.artwork}\n                        />\n                    </ThemedView>\n\n                    <ThemedView style={styles.controls}>\n\n                        <ThemedView style={styles.titleContainer}>\n                            <ThemedView style={styles.titleRow}>\n                                <ThemedView style={styles.titleMain}>\n                                    <ThemedText type=\"title\" style={styles.title}>\n                                        {currentSong?.title}\n                                    </ThemedText>\n                                    <ThemedText style={styles.artist}>\n                                        {currentSong?.artist}\n                                    </ThemedText>\n                                </ThemedView>\n                                <ThemedView style={styles.titleIcons}>\n                                    <Pressable style={styles.iconButton}>\n                                        <Ionicons name=\"star-outline\" size={18} color=\"#fff\" />\n                                    </Pressable>\n                                    <Pressable style={styles.iconButton}>\n                                        <Ionicons name=\"ellipsis-horizontal\" size={18} color=\"#fff\" />\n                                    </Pressable>\n                                </ThemedView>\n                            </ThemedView>\n\n                            <ThemedView style={styles.progressBar}>\n                                <ThemedView\n                                    style={[\n                                        styles.progress,\n                                        { width: `${progress}%` }\n                                    ]}\n                                />\n                            </ThemedView>\n\n                            <ThemedView style={styles.timeContainer}>\n                                <ThemedText style={styles.timeText}>\n                                    {formatTime(position)}\n                                </ThemedText>\n                                <ThemedText style={styles.timeText}>\n                                    -{formatTime(Math.max(0, duration - position))}\n                                </ThemedText>\n                            </ThemedView>\n\n\n\n                            <ThemedView style={styles.buttonContainer}>\n                                <Pressable style={styles.button} onPress={playPreviousSong}>\n                                    <Ionicons name=\"play-skip-back\" size={35} color=\"#fff\" />\n                                </Pressable>\n                                <Pressable style={[styles.button, styles.playButton]} onPress={togglePlayPause}>\n                                    <Ionicons name={isPlaying ? \"pause\" : \"play\"} size={45} color=\"#fff\" />\n                                </Pressable>\n                                <Pressable style={styles.button} onPress={playNextSong}>\n                                    <Ionicons name=\"play-skip-forward\" size={35} color=\"#fff\" />\n                                </Pressable>\n                            </ThemedView>\n\n\n                        </ThemedView>\n\n\n\n\n\n\n                        <ThemedView>\n                            <ThemedView style={styles.volumeControl}>\n                                <Ionicons name=\"volume-off\" size={24} color=\"#fff\" />\n                                <ThemedView style={styles.volumeBar}>\n                                    <ThemedView style={styles.volumeProgress} />\n                                </ThemedView>\n                                <Ionicons name=\"volume-high\" size={24} color=\"#fff\" />\n                            </ThemedView>\n\n\n\n\n\n                            <ThemedView style={styles.extraControls}>\n                                <Pressable style={styles.extraControlButton}>\n                                    <Ionicons name=\"chatbubble-outline\" size={24} color=\"#fff\" />\n                                </Pressable>\n\n                                <Pressable style={styles.extraControlButton}>\n                                    <ThemedView style={styles.extraControlIcons}>\n                                        <Ionicons name=\"volume-off\" size={26} color=\"#fff\" marginRight={-6} />\n                                        <Ionicons name=\"bluetooth\" size={24} color=\"#fff\" />\n                                    </ThemedView>\n                                    <ThemedText style={styles.extraControlText}>Px8</ThemedText>\n                                </Pressable>\n\n                                <Pressable style={styles.extraControlButton}>\n                                    <Ionicons name=\"list-outline\" size={24} color=\"#fff\" />\n                                </Pressable>\n                            </ThemedView>\n                        </ThemedView>\n\n\n\n                    </ThemedView>\n\n                    {/* Add lyrics section after the controls */}\n                    <ThemedView style={styles.lyricsContainer}>\n                        {lyrics.map((line, index) => (\n                            <ThemedText\n                                key={index}\n                                style={[\n                                    styles.lyricsText,\n                                    line === \"\" && styles.lyricsSpacing\n                                ]}\n                            >\n                                {line}\n                            </ThemedText>\n                        ))}\n                    </ThemedView>\n                </ThemedView>\n            </ScrollComponentToUse>\n        </LinearGradient>\n    );\n}\n\nconst styles = StyleSheet.create({\n    rootContainer: {\n        flex: 1,\n        height: '100%',\n        width: '100%',\n        borderTopLeftRadius: 40,\n        borderTopRightRadius: 40,\n    },\n    dragHandle: {\n        width: 40,\n        height: 5,\n        backgroundColor: 'rgba(255, 255, 255, 0.445)',\n        borderRadius: 5,\n        alignSelf: 'center',\n        marginTop: 10,\n    },\n    container: {\n        flex: 1,\n        alignItems: 'center',\n        padding: 20,\n        paddingTop: 30,\n\n        backgroundColor: 'transparent',\n        justifyContent: 'space-between',\n    },\n\n\n    artworkContainer: {\n        shadowColor: '#000',\n        shadowOffset: {\n            width: 0,\n            height: 8,\n        },\n        shadowOpacity: 0.4,\n        shadowRadius: 12,\n        elevation: 12,\n        backgroundColor: 'transparent', // Required for Android shadows\n        marginBottom: 34,\n    },\n    artwork: {\n        width: width - 52,\n        height: width - 52,\n        borderRadius: 8,\n    },\n    controls: {\n        width: '100%',\n        backgroundColor: 'transparent',\n        flex: 1,\n        justifyContent: 'space-between',\n    },\n    titleContainer: {\n        // marginBottom: -30,\n        backgroundColor: 'transparent',\n        width: '100%',\n        marginTop: 12\n    },\n    titleRow: {\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        alignItems: 'center',\n        width: '100%',\n    },\n    titleMain: {\n        flex: 1,\n    },\n    titleIcons: {\n        flexDirection: 'row',\n        gap: 15,\n    },\n    title: {\n        fontSize: 21,\n        // marginBottom: 8,\n        marginBottom: -4,\n        color: '#fff',\n    },\n    artist: {\n        fontSize: 19,\n        opacity: 0.7,\n        color: '#fff',\n    },\n    progressBar: {\n        height: 6,\n        backgroundColor: 'rgba(255, 255, 255, 0.3)',\n        borderRadius: 5,\n        marginBottom: 10,\n        marginTop: 30,\n    },\n    progress: {\n        width: '30%',\n        height: '100%',\n        backgroundColor: '#ffffff6a',\n        borderRadius: 5,\n        borderTopRightRadius: 0,\n        borderBottomRightRadius: 0,\n    },\n    timeContainer: {\n        flexDirection: 'row',\n        justifyContent: 'space-between',\n        marginBottom: 20,\n        backgroundColor: 'transparent',\n    },\n    timeText: {\n        fontSize: 12,\n        opacity: 0.6,\n        color: '#fff',\n    },\n    buttonContainer: {\n        flexDirection: 'row',\n        justifyContent: 'center',\n        alignItems: 'center',\n        gap: 50,\n        backgroundColor: 'transparent',\n        marginTop: 10,\n    },\n    button: {\n        padding: 10,\n    },\n    playButton: {\n        transform: [{ scale: 1.2 }],\n    },\n    volumeControl: {\n        flexDirection: 'row',\n        alignItems: 'center',\n        gap: 10,\n        paddingHorizontal: 10,\n\n    },\n    volumeBar: {\n        flex: 1,\n        height: 6,\n        backgroundColor: 'rgba(255, 255, 255, 0.3)',\n        borderRadius: 20,\n    },\n    volumeProgress: {\n        width: '70%',\n        height: '100%',\n        backgroundColor: '#fff',\n        borderRadius: 10,\n        borderTopRightRadius: 0,\n        borderBottomRightRadius: 0,\n    },\n    iconButton: {\n        width: 32,\n        height: 32,\n        borderRadius: 20,\n        backgroundColor: 'rgba(255, 255, 255, 0.2)',\n        justifyContent: 'center',\n        alignItems: 'center',\n    },\n    extraControls: {\n        flexDirection: 'row',\n        justifyContent: 'space-around',\n        alignItems: 'center',\n        width: '100%',\n        paddingHorizontal: 20,\n        marginTop: 26,\n        backgroundColor: 'transparent',\n\n    },\n    extraControlButton: {\n        alignItems: 'center',\n        // justifyContent: 'center',\n        opacity: 0.8,\n        height: 60,\n    },\n    extraControlText: {\n        color: '#fff',\n        fontSize: 13,\n        marginTop: 6,\n        opacity: 0.7,\n        fontWeight: '600',\n    },\n    extraControlIcons: {\n        flexDirection: 'row',\n\n    },\n    scrollView: {\n        flex: 1,\n        width: '100%',\n    },\n    lyricsContainer: {\n        paddingHorizontal: 20,\n        paddingVertical: 30,\n        width: '100%',\n        alignItems: 'center',\n    },\n    lyricsText: {\n        color: '#fff',\n        fontSize: 16,\n        lineHeight: 24,\n        textAlign: 'center',\n        opacity: 0.8,\n        marginVertical: 2,\n    },\n    lyricsSpacing: {\n        marginVertical: 10,\n    },\n    dragHandleContainer: {\n        paddingBottom: 14,\n    },\n});\n"
  },
  {
    "path": "components/BottomSheet/MiniPlayer.tsx",
    "content": "import { StyleSheet, Pressable, Image, Platform } from 'react-native';\nimport { ThemedText } from '@/components/ThemedText';\nimport { ThemedView } from '@/components/ThemedView';\nimport { Ionicons } from '@expo/vector-icons';\nimport { Audio } from 'expo-av';\nimport { useState, useEffect } from 'react';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\nimport { BlurView } from 'expo-blur';\nimport { useColorScheme } from '@/hooks/useColorScheme';\nimport { useAudio } from '@/contexts/AudioContext';\n\nexport function MiniPlayer({ onPress, song, isPlaying, onPlayPause }: MiniPlayerProps) {\n    const insets = useSafeAreaInsets();\n    const colorScheme = useColorScheme();\n\n    // Calculate bottom position considering tab bar height\n    const bottomPosition = Platform.OS === 'ios' ? insets.bottom + 57 : 60;\n\n    return (\n        <Pressable onPress={onPress} style={[\n            styles.container,\n            { bottom: bottomPosition }\n        ]}>\n            {Platform.OS === 'ios' ? (\n                <BlurView\n                    tint={colorScheme === 'dark' ? 'systemThickMaterialDark' : 'systemThickMaterialLight'}\n                    intensity={80}\n                    style={[styles.content, styles.blurContainer]}>\n                    <MiniPlayerContent song={song} isPlaying={isPlaying} onPlayPause={onPlayPause} />\n                </BlurView>\n            ) : (\n                <ThemedView style={[styles.content, styles.androidContainer]}>\n                    <MiniPlayerContent song={song} isPlaying={isPlaying} onPlayPause={onPlayPause} />\n                </ThemedView>\n            )}\n        </Pressable>\n    );\n}\n\n// Extract the content into a separate component for reusability\nfunction MiniPlayerContent({ song, isPlaying, onPlayPause }: {\n    song: any;\n    isPlaying: boolean;\n    onPlayPause: () => void;\n}) {\n    const colorScheme = useColorScheme();\n    const { playNextSong } = useAudio();\n\n    return (\n        <ThemedView style={[styles.miniPlayerContent, { backgroundColor: colorScheme === 'light' ? '#ffffffa4' : 'transparent' }]}>\n            <Image\n                source={{ uri: song.artwork }}\n                style={styles.artwork}\n            />\n            <ThemedView style={styles.textContainer}>\n                <ThemedText style={styles.title}>{song.title}</ThemedText>\n            </ThemedView>\n            <ThemedView style={styles.controls}>\n                <Pressable style={styles.controlButton} onPress={onPlayPause}>\n                    <Ionicons name={isPlaying ? \"pause\" : \"play\"} size={24} color={colorScheme === 'light' ? '#000' : '#fff'} />\n                </Pressable>\n                <Pressable style={styles.controlButton} onPress={playNextSong}>\n                    <Ionicons name=\"play-forward\" size={24} color={colorScheme === 'light' ? '#000' : '#fff'} />\n                </Pressable>\n            </ThemedView>\n        </ThemedView>\n    );\n}\n\nconst styles = StyleSheet.create({\n    container: {\n        position: 'absolute',\n        left: 0,\n        right: 0,\n        height: 56,\n        zIndex: 1000,\n        shadowColor: '#000',\n        shadowOffset: {\n            width: 0,\n            height: 2,\n        },\n        shadowOpacity: 0.15,\n        shadowRadius: 8,\n        elevation: 5,\n\n    },\n    content: {\n        flexDirection: 'row',\n        alignItems: 'center',\n        // height: 40,\n        marginHorizontal: 10,\n        borderRadius: 12,\n        overflow: 'hidden',\n        zIndex: 1000,\n        flex: 1,\n        paddingVertical: 0,\n\n\n    },\n    miniPlayerContent: {\n        flex: 1,\n        flexDirection: 'row',\n        alignItems: 'center',\n        height: '100%',\n        paddingHorizontal: 10,\n        // backgroundColor: '#ffffffa4',\n\n    },\n    blurContainer: {\n        // backgroundColor: '#00000000',\n    },\n    androidContainer: {\n\n    },\n    title: {\n        fontWeight: '500',\n    },\n    artwork: {\n        width: 40,\n        height: 40,\n        borderRadius: 8,\n    },\n    textContainer: {\n        flex: 1,\n        marginLeft: 12,\n        backgroundColor: 'transparent',\n    },\n    controls: {\n        flexDirection: 'row',\n        alignItems: 'center',\n        gap: 4,\n        marginRight: 4,\n        backgroundColor: 'transparent',\n    },\n    controlButton: {\n        padding: 8,\n    },\n});\n\ninterface MiniPlayerProps {\n    onPress: () => void;\n    song: any;\n    sound?: Audio.Sound | null;\n    isPlaying: boolean;\n    onPlayPause: () => void;\n}\n"
  },
  {
    "path": "components/CategoryCard.tsx",
    "content": "import { View, Text, Pressable, ImageBackground } from 'react-native';\nimport React from 'react';\nimport { Link } from 'expo-router';\n\ntype CategoryCardProps = {\n    title: string;\n    backgroundColor: string;\n    imageUrl?: string;\n    size?: 'large' | 'small';\n};\n\nexport function CategoryCard({ title, backgroundColor, imageUrl, size = 'small' }: CategoryCardProps) {\n    return (\n        <Link href={`/category/${title.toLowerCase()}`} asChild>\n            <Pressable>\n                <View\n                    style={{\n                        width: '100%',\n                        aspectRatio: 1.5,\n                        backgroundColor,\n                        borderRadius: 10,\n                        overflow: 'hidden',\n                    }}>\n                    {imageUrl && (\n                        <ImageBackground\n                            source={{ uri: imageUrl }}\n                            style={{\n                                width: '100%',\n                                height: '100%',\n                                justifyContent: 'flex-end',\n                            }}>\n                            <View style={{ padding: 16 }}>\n                                <Text\n                                    style={{\n                                        color: 'white',\n                                        fontSize: 24,\n                                        fontWeight: '600',\n                                    }}>\n                                    {title}\n                                </Text>\n                            </View>\n                        </ImageBackground>\n                    )}\n                    {!imageUrl && (\n                        <View style={{\n                            padding: 16,\n                            flex: 1,\n                            justifyContent: 'flex-end'\n                        }}>\n                            <Text\n                                style={{\n                                    color: 'white',\n                                    fontSize: 24,\n                                    fontWeight: '600',\n                                }}>\n                                {title}\n                            </Text>\n                        </View>\n                    )}\n                </View>\n            </Pressable>\n        </Link>\n    );\n} "
  },
  {
    "path": "components/MusicVisualizer.tsx",
    "content": "import { useEffect, useRef, useState } from 'react';\nimport { View, Animated, StyleSheet } from 'react-native';\n\ninterface Props {\n    isPlaying: boolean;\n}\n\nconst BAR_COUNT = 5;\nconst ANIMATION_DURATION = 300;\n\nexport function MusicVisualizer({ isPlaying }: Props) {\n    const animatedValues = useRef(\n        Array(BAR_COUNT).fill(0).map(() => new Animated.Value(0))\n    ).current;\n    const [prominentBar, setProminentBar] = useState(0);\n    const randomScales = useRef(Array(BAR_COUNT).fill(0).map(() => 0.3 + Math.random() * 0.4)).current;\n\n    useEffect(() => {\n        let prominentInterval: NodeJS.Timeout;\n\n        if (isPlaying) {\n            prominentInterval = setInterval(() => {\n                setProminentBar(prev => (prev + 1) % BAR_COUNT);\n                randomScales.forEach((_, i) => {\n                    randomScales[i] = 0.3 + Math.random() * 0.4;\n                });\n            }, 250);\n\n            const animations = animatedValues.map((value, index) => {\n                return Animated.sequence([\n                    Animated.timing(value, {\n                        toValue: 1,\n                        duration: ANIMATION_DURATION * (0.2 + Math.random() * 0.3),\n                        useNativeDriver: true,\n                    }),\n                    Animated.timing(value, {\n                        toValue: 0,\n                        duration: ANIMATION_DURATION * (0.2 + Math.random() * 0.3),\n                        useNativeDriver: true,\n                    }),\n                ]);\n            });\n\n            const loop = Animated.loop(Animated.parallel(animations));\n            loop.start();\n\n            return () => {\n                loop.stop();\n                clearInterval(prominentInterval);\n            };\n        } else {\n            animatedValues.forEach(value => value.setValue(0));\n        }\n    }, [isPlaying]);\n\n    if (!isPlaying) return null;\n\n    return (\n        <View style={styles.container}>\n            {animatedValues.map((value, index) => (\n                <Animated.View\n                    key={index}\n                    style={[\n                        styles.bar,\n                        {\n                            transform: [\n                                {\n                                    scaleY: value.interpolate({\n                                        inputRange: [0, 1],\n                                        outputRange: [0.2, index === prominentBar ? 1.4 : randomScales[index]],\n                                    }),\n                                },\n                            ],\n                        },\n                    ]}\n                />\n            ))}\n        </View>\n    );\n}\n\nconst styles = StyleSheet.create({\n    container: {\n        flexDirection: 'row',\n        alignItems: 'center',\n        justifyContent: 'center',\n        gap: 1.5,\n        position: 'absolute',\n        top: 0,\n        left: 0,\n        right: 0,\n        bottom: 0,\n        backgroundColor: 'rgba(0,0,0,0.3)',\n    },\n    bar: {\n        width: 2.5,\n        height: 16,\n        backgroundColor: '#fff',\n        borderRadius: 1,\n    },\n}); "
  },
  {
    "path": "components/Overlay/OverlayContext.tsx",
    "content": "import { createContext, useContext } from 'react';\nimport { ViewStyle } from 'react-native';\n\nexport interface OverlayView {\n    id: string;\n    component: React.ReactNode;\n    style?: ViewStyle;\n}\n\ninterface OverlayContextType {\n    views: OverlayView[];\n    addOverlay: (view: Omit<OverlayView, 'id'>) => string;\n    removeOverlay: (id: string) => void;\n}\n\nexport const OverlayContext = createContext<OverlayContextType>({\n    views: [],\n    addOverlay: () => '',\n    removeOverlay: () => { },\n});\n\nexport const useOverlay = () => useContext(OverlayContext);\n"
  },
  {
    "path": "components/Overlay/OverlayProvider.tsx",
    "content": "import React, { useState, useCallback } from 'react';\nimport { StyleSheet, View } from 'react-native';\nimport { OverlayContext, OverlayView } from './OverlayContext';\n\nexport const OverlayProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {\n    const [views, setViews] = useState<OverlayView[]>([]);\n\n    const addOverlay = useCallback((view: Omit<OverlayView, 'id'>) => {\n        const id = Math.random().toString(36).substr(2, 9);\n        setViews(prev => [...prev, { ...view, id }]);\n        return id;\n    }, []);\n\n    const removeOverlay = useCallback((id: string) => {\n        setViews(prev => prev.filter(view => view.id !== id));\n    }, []);\n\n    return (\n        <OverlayContext.Provider value={{ views, addOverlay, removeOverlay }}>\n            <View style={styles.container}>\n                {children}\n                {views.map(view => (\n                    <View key={view.id} style={[styles.overlay, view.style]}>\n                        {view.component}\n                    </View>\n                ))}\n            </View>\n        </OverlayContext.Provider>\n    );\n};\n\nconst styles = StyleSheet.create({\n    container: {\n        flex: 1,\n    },\n    overlay: {\n        ...StyleSheet.absoluteFillObject,\n        backgroundColor: 'transparent',\n    },\n});\n"
  },
  {
    "path": "components/ParallaxScrollView.tsx",
    "content": "import type { PropsWithChildren, ReactElement } from 'react';\nimport { StyleSheet, useColorScheme } from 'react-native';\nimport Animated, {\n  interpolate,\n  useAnimatedRef,\n  useAnimatedStyle,\n  useScrollViewOffset,\n} from 'react-native-reanimated';\n\nimport { ThemedView } from '@/components/ThemedView';\n\nconst HEADER_HEIGHT = 300;\n\ntype Props = PropsWithChildren<{\n  headerImage: ReactElement;\n  headerBackgroundColor: { dark: string; light: string };\n}>;\n\nexport default function ParallaxScrollView({\n  children,\n  headerImage,\n  headerBackgroundColor,\n}: Props) {\n  const colorScheme = useColorScheme() ?? 'light';\n  const scrollRef = useAnimatedRef<Animated.ScrollView>();\n  const scrollOffset = useScrollViewOffset(scrollRef);\n\n  const headerAnimatedStyle = useAnimatedStyle(() => {\n    return {\n      transform: [\n        {\n          translateY: interpolate(\n            scrollOffset.value,\n            [-HEADER_HEIGHT, 0, HEADER_HEIGHT],\n            [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75]\n          ),\n        },\n        {\n          scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]),\n        },\n      ],\n    };\n  });\n\n  return (\n    <ThemedView style={styles.container}>\n      <Animated.ScrollView ref={scrollRef} scrollEventThrottle={16}>\n        <Animated.View\n          style={[\n            styles.header,\n            { backgroundColor: headerBackgroundColor[colorScheme] },\n            headerAnimatedStyle,\n          ]}>\n          {headerImage}\n        </Animated.View>\n        <ThemedView style={styles.content}>{children}</ThemedView>\n      </Animated.ScrollView>\n    </ThemedView>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  header: {\n    height: 280,\n    overflow: 'hidden',\n  },\n  content: {\n    flex: 1,\n    // padding: 20,\n    gap: 16,\n    overflow: 'hidden',\n  },\n});\n"
  },
  {
    "path": "components/ThemedText.tsx",
    "content": "import { Text, type TextProps, StyleSheet } from 'react-native';\n\nimport { useThemeColor } from '@/hooks/useThemeColor';\n\nexport type ThemedTextProps = TextProps & {\n  lightColor?: string;\n  darkColor?: string;\n  type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';\n};\n\nexport function ThemedText({\n  style,\n  lightColor,\n  darkColor,\n  type = 'default',\n  ...rest\n}: ThemedTextProps) {\n  const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');\n\n  return (\n    <Text\n      style={[\n        { color },\n        type === 'default' ? styles.default : undefined,\n        type === 'title' ? styles.title : undefined,\n        type === 'defaultSemiBold' ? styles.defaultSemiBold : undefined,\n        type === 'subtitle' ? styles.subtitle : undefined,\n        type === 'link' ? styles.link : undefined,\n        style,\n      ]}\n      {...rest}\n    />\n  );\n}\n\nconst styles = StyleSheet.create({\n  default: {\n    fontSize: 16,\n    lineHeight: 24,\n  },\n  defaultSemiBold: {\n    fontSize: 16,\n    lineHeight: 24,\n    fontWeight: '600',\n  },\n  title: {\n    fontSize: 32,\n    fontWeight: 'bold',\n    lineHeight: 32,\n  },\n  subtitle: {\n    fontSize: 20,\n    fontWeight: 'bold',\n  },\n  link: {\n    lineHeight: 30,\n    fontSize: 16,\n    color: '#0a7ea4',\n  },\n});\n"
  },
  {
    "path": "components/ThemedView.tsx",
    "content": "import { View, type ViewProps } from 'react-native';\n\nimport { useThemeColor } from '@/hooks/useThemeColor';\n\nexport type ThemedViewProps = ViewProps & {\n  lightColor?: string;\n  darkColor?: string;\n};\n\nexport function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {\n  const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');\n\n  return <View style={[{ backgroundColor }, style]} {...otherProps} />;\n}\n"
  },
  {
    "path": "components/navigation/TabBarIcon.tsx",
    "content": "import Ionicons from '@expo/vector-icons/Ionicons';\nimport { type IconProps } from '@expo/vector-icons/build/createIconSet';\nimport { type ComponentProps } from 'react';\n\nexport function TabBarIcon({ style, ...rest }: IconProps<ComponentProps<typeof Ionicons>['name']>) {\n  return <Ionicons size={28} style={[{ marginBottom: -3 }, style]} {...rest} />;\n}\n"
  },
  {
    "path": "constants/Colors.ts",
    "content": "/**\n * Below are the colors that are used in the app. The colors are defined in the light and dark mode.\n * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.\n */\n\nconst tintColorLight = '#0a7ea4';\nconst tintColorDark = '#fff';\n\nexport const Colors = {\n  light: {\n    text: '#11181C',\n    background: '#fff',\n    tint: tintColorLight,\n    icon: '#687076',\n    tabIconDefault: '#687076',\n    tabIconSelected: tintColorLight,\n  },\n  dark: {\n    text: '#ECEDEE',\n    background: '#151718',\n    tint: tintColorDark,\n    icon: '#9BA1A6',\n    tabIconDefault: '#9BA1A6',\n    tabIconSelected: tintColorDark,\n  },\n};\n"
  },
  {
    "path": "contexts/AudioContext.tsx",
    "content": "import { createContext, useContext, useState, useEffect, useCallback } from 'react';\nimport { Audio } from 'expo-av';\nimport { songs } from '@/data/songs.json';\n\ninterface Song {\n    id: number;\n    title: string;\n    artist: string;\n    artwork: string;\n    artwork_bg_color?: string;\n    mp4_link?: string;\n}\n\ninterface AudioContextType {\n    sound: Audio.Sound | null;\n    isPlaying: boolean;\n    currentSong: Song | null;\n    position: number;\n    duration: number;\n    setSound: (sound: Audio.Sound | null) => void;\n    setIsPlaying: (isPlaying: boolean) => void;\n    setCurrentSong: (song: Song) => void;\n    playSound: (song: Song) => Promise<void>;\n    pauseSound: () => Promise<void>;\n    togglePlayPause: () => Promise<void>;\n    playNextSong: () => Promise<void>;\n    playPreviousSong: () => Promise<void>;\n}\n\nconst AudioContext = createContext<AudioContextType | undefined>(undefined);\n\nexport function AudioProvider({ children }: { children: React.ReactNode }) {\n    const [sound, setSound] = useState<Audio.Sound | null>(null);\n    const [isPlaying, setIsPlaying] = useState(false);\n    const [currentSong, setCurrentSong] = useState<Song | null>(null);\n    const [position, setPosition] = useState(0);\n    const [duration, setDuration] = useState(0);\n\n    useEffect(() => {\n        return () => {\n            if (sound) {\n                sound.unloadAsync();\n            }\n        };\n    }, []);\n\n    useEffect(() => {\n        const setupAudio = async () => {\n            try {\n                await Audio.setAudioModeAsync({\n                    playsInSilentModeIOS: true,\n                    staysActiveInBackground: true,\n                    shouldDuckAndroid: true,\n                });\n            } catch (error) {\n                console.error('Error setting up audio mode:', error);\n            }\n        };\n\n        setupAudio();\n    }, []);\n\n    const playSound = async (song: Song) => {\n        try {\n            // If there's already a sound playing, stop it\n            if (sound) {\n                await sound.unloadAsync();\n            }\n\n            const { sound: newSound } = await Audio.Sound.createAsync(\n                { uri: song.mp4_link },\n                { shouldPlay: true },\n                onPlaybackStatusUpdate\n            );\n\n            setSound(newSound);\n            setCurrentSong(song);\n            setIsPlaying(true);\n            await newSound.playAsync();\n        } catch (error) {\n            console.error('Error playing sound:', error);\n        }\n    };\n\n    const pauseSound = async () => {\n        if (sound) {\n            await sound.pauseAsync();\n            setIsPlaying(false);\n        }\n    };\n\n    const togglePlayPause = async () => {\n        if (!sound || !currentSong) return;\n\n        if (isPlaying) {\n            await pauseSound();\n        } else {\n            await sound.playAsync();\n            setIsPlaying(true);\n        }\n    };\n\n    const onPlaybackStatusUpdate = useCallback(async (status: Audio.PlaybackStatus) => {\n        if (!status.isLoaded) return;\n\n        setPosition(status.positionMillis);\n        setDuration(status.durationMillis || 0);\n        setIsPlaying(status.isPlaying);\n\n        // Check if the song has finished and isn't already loading the next song\n        if (status.didJustFinish && !status.isPlaying) {\n            console.log('Song finished, playing next song'); // Debug log\n            await playNextSong();\n        }\n    }, [playNextSong]);\n\n    const playNextSong = useCallback(async () => {\n        const currentIndex = songs.findIndex(song => song.id === currentSong?.id);\n        if (currentIndex === -1) return;\n\n        const nextSong = songs[(currentIndex + 1) % songs.length];\n        await playSound(nextSong);\n    }, [currentSong, songs]);\n\n    const playPreviousSong = useCallback(async () => {\n        const currentIndex = songs.findIndex(song => song.id === currentSong?.id);\n        if (currentIndex === -1) return;\n\n        const previousIndex = currentIndex === 0 ? songs.length - 1 : currentIndex - 1;\n        const previousSong = songs[previousIndex];\n        await playSound(previousSong);\n    }, [currentSong, songs]);\n\n    return (\n        <AudioContext.Provider value={{\n            sound,\n            isPlaying,\n            currentSong,\n            position,\n            duration,\n            setSound,\n            setIsPlaying,\n            setCurrentSong,\n            playSound,\n            pauseSound,\n            togglePlayPause,\n            playNextSong,\n            playPreviousSong,\n        }}>\n            {children}\n        </AudioContext.Provider>\n    );\n}\n\nexport function useAudio() {\n    const context = useContext(AudioContext);\n    if (context === undefined) {\n        throw new Error('useAudio must be used within an AudioProvider');\n    }\n    return context;\n}\n"
  },
  {
    "path": "contexts/RootScaleContext.tsx",
    "content": "import React, { createContext, useContext } from 'react';\nimport { SharedValue, useSharedValue, withSpring } from 'react-native-reanimated';\n\ninterface RootScaleContextType {\n    scale: SharedValue<number>;\n    setScale: (value: number) => void;\n}\n\nconst RootScaleContext = createContext<RootScaleContextType | null>(null);\n\nexport function RootScaleProvider({ children }: { children: React.ReactNode }) {\n    const scale = useSharedValue(1);\n\n    const setScale = (value: number) => {\n        'worklet';\n        try {\n            scale.value = withSpring(value, {\n                damping: 15,\n                stiffness: 150,\n                mass: 0.5, // Added for smoother animation\n            });\n        } catch (error) {\n            console.warn('Scale animation error:', error);\n            scale.value = value;\n        }\n    };\n\n    return (\n        <RootScaleContext.Provider value={{ scale, setScale }}>\n            {children}\n        </RootScaleContext.Provider>\n    );\n}\n\nexport const useRootScale = () => {\n    const context = useContext(RootScaleContext);\n    if (!context) {\n        throw new Error('useRootScale must be used within a RootScaleProvider');\n    }\n    return context;\n};\n"
  },
  {
    "path": "data/songs.json",
    "content": "{\n    \"songs\": [\n        {\n            \"id\": 0,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview221/v4/72/53/c7/7253c7f2-b61f-baf3-3b4e-08171e35cfea/mzaf_921634286400386475.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/54/50/75/54507577-9e1d-c2c0-79dd-81e65aa7fb9d/197342550925_cover.jpg/247x247bb.jpg\",\n            \"title\": \"A Bar Song (Tipsy)\",\n            \"artist\": \"Shaboozey\",\n            \"artwork_bg_color\": \"#8B4513\"\n        },\n        {\n            \"id\": 2,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview211/v4/ca/08/ce/ca08ce69-fa1f-e9bf-16da-dfd34aa80b49/mzaf_4558229648643978920.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/92/9f/69/929f69f1-9977-3a44-d674-11f70c852d1b/24UMGIM36186.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"Birds Of A Feather\",\n            \"artist\": \"Billie Eilish\",\n            \"artwork_bg_color\": \"#2F4F4F\"\n        },\n        {\n            \"id\": 3,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview211/v4/97/a2/b8/97a2b874-e3de-5658-f889-5d0eedb72d3f/mzaf_13627043593740003520.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/cb/64/8c/cb648cf7-e7bb-bd00-efe0-d744312c29a8/24UMGIM32304.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"Espresso\",\n            \"artist\": \"Sabrina Carpenter\",\n            \"artwork_bg_color\": \"#CD853F\"\n        },\n        {\n            \"id\": 4,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview211/v4/e9/d1/46/e9d14699-9505-493e-cd27-a501095c81ff/mzaf_7283388936457278756.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/11/ae/f2/11aef294-f57c-bab9-c9fc-529162984e62/24UMGIM85348.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"Die With A Smile\",\n            \"artist\": \"Lady Gaga & Bruno Mars\",\n            \"artwork_bg_color\": \"#800080\"\n        },\n        {\n            \"id\": 5,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview211/v4/c4/fa/e2/c4fae20a-494c-e06f-99e3-b6e820f8cc87/mzaf_7882276521788841354.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/e1/b7/09/e1b7098f-d6e1-2b18-fd6f-8390110908eb/24UMGIM50612.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"I Had Some Help\",\n            \"artist\": \"Post Malone Featuring Morgan Wallen\",\n            \"artwork_bg_color\": \"#4B0082\"\n        },\n        {\n            \"id\": 6,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview116/v4/3a/d0/30/3ad03076-a30e-9a8d-46bf-3cf3e36e31c9/mzaf_15630973341141916680.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/36/19/66/36196640-1561-dc5e-c6bc-1e5f4befa583/093624856771.jpg/247x247bb.jpg\",\n            \"title\": \"Lose Control\",\n            \"artist\": \"Teddy Swims\",\n            \"artwork_bg_color\": \"#FF4500\"\n        },\n        {\n            \"id\": 7,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview211/v4/ae/57/5d/ae575db4-68db-508a-c012-421ee79bfa62/mzaf_5112451481032254728.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/29/a7/c4/29a7c478-351d-25eb-a116-3e68118cdab8/24UMGIM31246.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"Good Luck, Babe!\",\n            \"artist\": \"Chappell Roan\",\n            \"artwork_bg_color\": \"#a55f47\"\n        },\n        {\n            \"id\": 8,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview112/v4/bb/6b/c8/bb6bc802-78d2-c0b9-1434-cdcd7c6c7eb3/mzaf_5610996391320604947.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music115/v4/a5/c4/24/a5c424a5-8084-47ab-3fa8-e82f00faf1bf/888915632314_cover.jpg/247x247bb.jpg\",\n            \"title\": \"Taste\",\n            \"artist\": \"Sabrina Carpenter\",\n            \"artwork_bg_color\": \"#FF1493\"\n        },\n        {\n            \"id\": 9,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview126/v4/2d/4e/7b/2d4e7b94-5521-568f-f269-c8643001d32b/mzaf_6034909346296341668.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music116/v4/54/f4/92/54f49210-e260-b519-ebbd-f4f40ee710cd/054391342751.jpg/247x247bb.jpg\",\n            \"title\": \"Beautiful Things\",\n            \"artist\": \"Benson Boone\",\n            \"artwork_bg_color\": \"#4682B4\"\n        },\n        {\n            \"id\": 10,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview221/v4/78/6b/15/786b15ad-8958-8019-28e2-a29f1c64f9d4/mzaf_9652925359746163912.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/f6/97/58/f69758d6-24d8-6e44-be5b-26819921bfc7/24UMGIM61704.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"Please Please Please\",\n            \"artist\": \"Sabrina Carpenter\",\n            \"artwork_bg_color\": \"#DA70D6\"\n        },\n        {\n            \"id\": 12,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview221/v4/df/05/e7/df05e74c-c4a1-b6ca-61b0-e2c36df3ebfd/mzaf_9714719170473697657.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/d0/ef/b6/d0efb685-73be-fdee-58c9-be655f4cd4fd/24UMGIM51924.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"Not Like Us\",\n            \"artist\": \"Kendrick Lamar\",\n            \"artwork_bg_color\": \"#483D8B\"\n        },\n        {\n            \"id\": 13,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview211/v4/58/77/2b/58772b8f-f76e-faaf-31dd-db5d8c57376e/mzaf_7312578909270296724.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/75/43/b9/7543b9da-b57f-4378-7eaf-1e02b49f5cc5/24UMGIM21983.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"Too Sweet\",\n            \"artist\": \"Hozier\",\n            \"artwork_bg_color\": \"#8B4513\"\n        },\n        {\n            \"id\": 14,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview211/v4/dd/4e/e4/dd4ee44f-4c61-68f9-d8e1-3bf5e87dc0d9/mzaf_2623955613726623758.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/ef/14/11/ef14117b-6681-088b-3c74-e127ca3b46ed/24UM1IM04061.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"Timeless\",\n            \"artist\": \"The Weeknd & Playboi Carti\",\n            \"artwork_bg_color\": \"#000000\"\n        },\n        {\n            \"id\": 15,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview221/v4/b1/bf/90/b1bf9092-a386-33a9-8312-ce9ee450d317/mzaf_4279519707152699387.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/95/b9/ca/95b9ca00-29cb-8edc-1ecb-5bda742f3177/24UMGIM62166.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"I Am Not Okay\",\n            \"artist\": \"Jelly Roll\",\n            \"artwork_bg_color\": \"#8B0000\"\n        },\n        {\n            \"id\": 16,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview211/v4/ef/96/da/ef96da4c-8118-a432-bdc2-5ac2c05627a1/mzaf_5100727894092760169.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/35/01/1b/35011b7f-f9d2-5ddc-3759-55076a59aaa7/193436381567_mdbcln.jpg/247x247bb.jpg\",\n            \"title\": \"Million Dollar Baby\",\n            \"artist\": \"Tommy Richman\",\n            \"artwork_bg_color\": \"#FFD700\"\n        },\n        {\n            \"id\": 17,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview126/v4/b3/be/aa/b3beaa10-2425-d26b-7036-4dfa66fd96e0/mzaf_9494109365642641249.plus.aac.p.m4a\",\n            \"artwork\": \"https://is3-ssl.mzstatic.com/image/thumb/Music116/v4/89/e0/59/89e0595b-6cfb-ee43-eb31-89e5eb8c3a69/634904074050.png/247x247bb.jpg\",\n            \"title\": \"25\",\n            \"artist\": \"Rod Wave\",\n            \"artwork_bg_color\": \"#4169E1\"\n        },\n        {\n            \"id\": 18,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview211/v4/1a/ba/8f/1aba8f84-d6d7-ba3f-222a-1bab26cee4c6/mzaf_7418060965643085123.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/e2/53/2f/e2532f80-b877-c273-938d-85565456cba4/24UMGIM69835.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"Lies Lies Lies\",\n            \"artist\": \"Morgan Wallen\",\n            \"artwork_bg_color\": \"#CD853F\"\n        },\n        {\n            \"id\": 19,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview126/v4/69/1f/70/691f70d8-3dd2-97e3-2edb-9f8cbe90cb16/mzaf_18194676297747432902.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music126/v4/42/a0/c5/42a0c5e6-6b98-f1f9-7d6b-6d6c61aba562/23UMGIM84225.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"Hot To Go!\",\n            \"artist\": \"Chappell Roan\",\n            \"artwork_bg_color\": \"#FF69B4\"\n        },\n        {\n            \"id\": 20,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview221/v4/c8/ef/8c/c8ef8c39-9e98-f2cc-6c6f-5aeb0df0b64f/mzaf_10384034394539215321.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/52/9a/a7/529aa76f-5d60-cd81-9eb0-0eb521de861d/24UMGIM43968.rgb.jpg/247x247bb.jpg\",\n            \"title\": \"I Love You, I'm Sorry\",\n            \"artist\": \"Gracie Abrams\",\n            \"artwork_bg_color\": \"#B0C4DE\"\n        },\n        {\n            \"id\": 21,\n            \"mp4_link\": \"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview211/v4/71/e0/c7/71e0c7d0-2123-77bd-2322-e9a8d11333e5/mzaf_12415459325798775890.plus.aac.p.m4a\",\n            \"artwork\": \"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/16/5e/2d/165e2ddb-c596-8565-d86a-6231b4a911a6/196872075496.jpg/247x247bb.jpg\",\n            \"title\": \"Miles On It\",\n            \"artist\": \"Marshmello & Kane Brown\",\n            \"artwork_bg_color\": \"#F0F8FF\"\n        }\n    ]\n}"
  },
  {
    "path": "hooks/useColorScheme.ts",
    "content": "export { useColorScheme } from 'react-native';\n"
  },
  {
    "path": "hooks/useColorScheme.web.ts",
    "content": "// NOTE: The default React Native styling doesn't support server rendering.\n// Server rendered styles should not change between the first render of the HTML\n// and the first render on the client. Typically, web developers will use CSS media queries\n// to render different styles on the client and server, these aren't directly supported in React Native\n// but can be achieved using a styling library like Nativewind.\nexport function useColorScheme() {\n  return 'light';\n}\n"
  },
  {
    "path": "hooks/useOverlayView.ts",
    "content": "import { useCallback, useEffect, useRef } from 'react';\nimport { useOverlay } from '@/components/Overlay/OverlayContext';\n\nexport const useOverlayView = () => {\n    const { addOverlay, removeOverlay } = useOverlay();\n    const overlayIdRef = useRef<string | null>(null);\n\n    const show = useCallback((component: React.ReactNode) => {\n        if (overlayIdRef.current) {\n            removeOverlay(overlayIdRef.current);\n        }\n        overlayIdRef.current = addOverlay({ component });\n    }, [addOverlay, removeOverlay]);\n\n    const hide = useCallback(() => {\n        if (overlayIdRef.current) {\n            removeOverlay(overlayIdRef.current);\n            overlayIdRef.current = null;\n        }\n    }, [removeOverlay]);\n\n    useEffect(() => {\n        return () => {\n            if (overlayIdRef.current) {\n                removeOverlay(overlayIdRef.current);\n            }\n        };\n    }, [removeOverlay]);\n\n    return { show, hide };\n};\n"
  },
  {
    "path": "hooks/useThemeColor.ts",
    "content": "/**\n * Learn more about light and dark modes:\n * https://docs.expo.dev/guides/color-schemes/\n */\n\nimport { useColorScheme } from 'react-native';\n\nimport { Colors } from '@/constants/Colors';\n\nexport function useThemeColor(\n  props: { light?: string; dark?: string },\n  colorName: keyof typeof Colors.light & keyof typeof Colors.dark\n) {\n  const theme = useColorScheme() ?? 'light';\n  const colorFromProps = props[theme];\n\n  if (colorFromProps) {\n    return colorFromProps;\n  } else {\n    return Colors[theme][colorName];\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"apple-music-sheet-ui\",\n  \"main\": \"expo-router/entry\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"start\": \"expo start\",\n    \"reset-project\": \"node ./scripts/reset-project.js\",\n    \"android\": \"expo start --android\",\n    \"ios\": \"expo start --ios\",\n    \"web\": \"expo start --web\",\n    \"test\": \"jest --watchAll\",\n    \"lint\": \"expo lint\"\n  },\n  \"jest\": {\n    \"preset\": \"jest-expo\"\n  },\n  \"dependencies\": {\n    \"@expo/vector-icons\": \"^14.0.2\",\n    \"@react-navigation/native\": \"^6.0.2\",\n    \"expo\": \"~51.0.28\",\n    \"expo-constants\": \"~16.0.2\",\n    \"expo-font\": \"~12.0.9\",\n    \"expo-linking\": \"~6.3.1\",\n    \"expo-router\": \"~3.5.23\",\n    \"expo-splash-screen\": \"~0.27.5\",\n    \"expo-status-bar\": \"~1.12.1\",\n    \"expo-system-ui\": \"~3.0.7\",\n    \"expo-web-browser\": \"~13.0.3\",\n    \"react\": \"18.2.0\",\n    \"react-dom\": \"18.2.0\",\n    \"react-native\": \"0.74.5\",\n    \"react-native-gesture-handler\": \"~2.16.1\",\n    \"react-native-reanimated\": \"~3.10.1\",\n    \"react-native-safe-area-context\": \"4.10.5\",\n    \"react-native-screens\": \"3.31.1\",\n    \"react-native-web\": \"~0.19.10\",\n    \"expo-linear-gradient\": \"~13.0.2\",\n    \"expo-av\": \"~14.0.7\",\n    \"expo-blur\": \"~13.0.2\",\n    \"expo-haptics\": \"~13.0.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.20.0\",\n    \"@types/jest\": \"^29.5.12\",\n    \"@types/react\": \"~18.2.45\",\n    \"@types/react-test-renderer\": \"^18.0.7\",\n    \"jest\": \"^29.2.1\",\n    \"jest-expo\": \"~51.0.3\",\n    \"react-test-renderer\": \"18.2.0\",\n    \"typescript\": \"~5.3.3\"\n  },\n  \"private\": true\n}\n"
  },
  {
    "path": "scripts/reset-project.js",
    "content": "#!/usr/bin/env node\n\n/**\n * This script is used to reset the project to a blank state.\n * It moves the /app directory to /app-example and creates a new /app directory with an index.tsx and _layout.tsx file.\n * You can remove the `reset-project` script from package.json and safely delete this file after running it.\n */\n\nconst fs = require('fs');\nconst path = require('path');\n\nconst root = process.cwd();\nconst oldDirPath = path.join(root, 'app');\nconst newDirPath = path.join(root, 'app-example');\nconst newAppDirPath = path.join(root, 'app');\n\nconst indexContent = `import { Text, View } from \"react-native\";\n\nexport default function Index() {\n  return (\n    <View\n      style={{\n        flex: 1,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n      }}\n    >\n      <Text>Edit app/index.tsx to edit this screen.</Text>\n    </View>\n  );\n}\n`;\n\nconst layoutContent = `import { Stack } from \"expo-router\";\n\nexport default function RootLayout() {\n  return (\n    <Stack>\n      <Stack.Screen name=\"index\" />\n    </Stack>\n  );\n}\n`;\n\nfs.rename(oldDirPath, newDirPath, (error) => {\n  if (error) {\n    return console.error(`Error renaming directory: ${error}`);\n  }\n  console.log('/app moved to /app-example.');\n\n  fs.mkdir(newAppDirPath, { recursive: true }, (error) => {\n    if (error) {\n      return console.error(`Error creating new app directory: ${error}`);\n    }\n    console.log('New /app directory created.');\n\n    const indexPath = path.join(newAppDirPath, 'index.tsx');\n    fs.writeFile(indexPath, indexContent, (error) => {\n      if (error) {\n        return console.error(`Error creating index.tsx: ${error}`);\n      }\n      console.log('app/index.tsx created.');\n\n      const layoutPath = path.join(newAppDirPath, '_layout.tsx');\n      fs.writeFile(layoutPath, layoutContent, (error) => {\n        if (error) {\n          return console.error(`Error creating _layout.tsx: ${error}`);\n        }\n        console.log('app/_layout.tsx created.');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"expo/tsconfig.base\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"paths\": {\n      \"@/*\": [\n        \"./*\"\n      ]\n    }\n  },\n  \"include\": [\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".expo/types/**/*.ts\",\n    \"expo-env.d.ts\"\n  ]\n}\n"
  }
]