[
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n.vercel\n"
  },
  {
    "path": "README.md",
    "content": "# GameHub\n\nGameHub is a video game discovery web app that helps you find new and interesting games to play. With GameHub, you can search for games by platform, genre, and more. \n\nThis is the project we build in my Ultimate React course. You can find the full course at: https://codewithmosh.com \n\n## Getting Started\n\nTo get started with GameHub, follow these steps:\n\n\n1. Clone this repository to your local machine.\n2. Run `npm install` to install the required dependencies.\n3. Get a RAWG API key at https://rawg.io/apidocs. You'll have to create an account first. \n4. Add the API key to **src/services/api-client.ts**\n5. Run `npm run dev` to start the web server. \n\n## About the Course \n\nI have designed this course to teach you everything you need to know to become a proficient React developer. This course is the first part of a two-part series, covering the fundamentals. You'll learn how to:\n\n- Build front-end apps with React and TypeScript\n- Build reusable function components\n- Style your components using vanilla CSS, CSS modules, and CSS-in-JS\n- Manage component state\n- Build forms with React Hook Forms\n- Implement form validation using Zod\n- Connect your React apps to the backend\n- Deploy your React apps\n- Use VSCode shortcuts to increase your productivity\n- Write clean code like a pro\n- Apply best practices\n\n\nBy the end of this course, you'll have a solid understanding of React and be able to build real-world applications with React and TypeScript.\n\nYou can find the full course at: https://codewithmosh.com \n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>GameHub</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"game-hub\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@chakra-ui/react\": \"^2.5.1\",\n    \"@emotion/react\": \"^11.10.6\",\n    \"@emotion/styled\": \"^11.10.6\",\n    \"axios\": \"^1.3.4\",\n    \"framer-motion\": \"^10.0.1\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-icons\": \"^4.7.1\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^18.0.27\",\n    \"@types/react-dom\": \"^18.0.10\",\n    \"@vitejs/plugin-react\": \"^3.1.0\",\n    \"typescript\": \"^4.9.3\",\n    \"vite\": \"^4.1.0\"\n  }\n}\n"
  },
  {
    "path": "src/App.css",
    "content": "#root {\n  max-width: 1280px;\n  margin: 0 auto;\n  padding: 2rem;\n  text-align: center;\n}\n\n.logo {\n  height: 6em;\n  padding: 1.5em;\n  will-change: filter;\n  transition: filter 300ms;\n}\n.logo:hover {\n  filter: drop-shadow(0 0 2em #646cffaa);\n}\n.logo.react:hover {\n  filter: drop-shadow(0 0 2em #61dafbaa);\n}\n\n@keyframes logo-spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  a:nth-of-type(2) .logo {\n    animation: logo-spin infinite 20s linear;\n  }\n}\n\n.card {\n  padding: 2em;\n}\n\n.read-the-docs {\n  color: #888;\n}\n"
  },
  {
    "path": "src/App.tsx",
    "content": "import { Box, Flex, Grid, GridItem, HStack, Show } from \"@chakra-ui/react\";\nimport { useState } from \"react\";\nimport GameGrid from \"./components/GameGrid\";\nimport GameHeading from \"./components/GameHeading\";\nimport GenreList from \"./components/GenreList\";\nimport NavBar from \"./components/NavBar\";\nimport PlatformSelector from \"./components/PlatformSelector\";\nimport SortSelector from \"./components/SortSelector\";\nimport { Platform } from \"./hooks/useGames\";\nimport { Genre } from \"./hooks/useGenres\";\n\n\nexport interface GameQuery { \n  genre: Genre | null;\n  platform: Platform | null;\n  sortOrder: string;\n  searchText: string;\n}\n\nfunction App() {\n  const [gameQuery, setGameQuery] = useState<GameQuery>({} as GameQuery);\n  \n  return (\n    <Grid\n      templateAreas={{\n        base: `\"nav\" \"main\"`,\n        lg: `\"nav nav\" \"aside main\"`,\n      }}\n      templateColumns={{\n        base: '1fr',\n        lg: '250px 1fr'\n      }}\n    >\n      <GridItem area=\"nav\">\n        <NavBar onSearch={(searchText) => setGameQuery({ ...gameQuery, searchText })} />\n      </GridItem>\n      <Show above=\"lg\">\n        <GridItem area=\"aside\" paddingX={5}>\n          <GenreList selectedGenre={gameQuery.genre} onSelectGenre={(genre) => setGameQuery({ ...gameQuery, genre})} />\n        </GridItem>\n      </Show>\n      <GridItem area=\"main\">\n        <Box paddingLeft={2}>\n          <GameHeading gameQuery={gameQuery} />\n          <Flex marginBottom={5}>\n            <Box marginRight={5}>\n              <PlatformSelector selectedPlatform={gameQuery.platform} onSelectPlatform={(platform) => setGameQuery({ ...gameQuery, platform}) } />\n            </Box>\n            <SortSelector sortOrder={gameQuery.sortOrder} onSelectSortOrder={(sortOrder) => setGameQuery({ ...gameQuery, sortOrder })} />\n          </Flex>\n        </Box>\n        <GameGrid gameQuery={gameQuery} />\n      </GridItem>\n    </Grid>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "src/components/ColorModeSwitch.tsx",
    "content": "import { HStack, Switch, Text, useColorMode } from '@chakra-ui/react'\n\nconst ColorModeSwitch = () => {\n  const {toggleColorMode, colorMode} = useColorMode();\n\n  return (\n    <HStack>\n      <Switch colorScheme='green' isChecked={colorMode === 'dark'} onChange={toggleColorMode} />\n      <Text whiteSpace='nowrap'>Dark Mode</Text>\n    </HStack>\n  )\n}\n\nexport default ColorModeSwitch"
  },
  {
    "path": "src/components/CriticScore.tsx",
    "content": "import { Badge } from '@chakra-ui/react';\n\ninterface Props { \n  score: number;\n}\n\nconst CriticScore = ({ score }: Props) => {\n  let color = score > 75 ? 'green' : score > 60 ? 'yellow' : '';\n\n  return (\n    <Badge colorScheme={color} fontSize='14px' paddingX={2} borderRadius='4px'>{score}</Badge>\n  )\n}\n\nexport default CriticScore"
  },
  {
    "path": "src/components/Emoji.tsx",
    "content": "import bullsEye from '../assets/bulls-eye.webp';\nimport thumbsUp from '../assets/thumbs-up.webp';\nimport meh from '../assets/meh.webp';\nimport { Image, ImageProps } from '@chakra-ui/react';\n\ninterface Props {\n  rating: number;\n}\n\nconst Emoji = ({ rating }: Props) => {\n  if (rating < 3) return null;\n\n  const emojiMap: { [key: number]: ImageProps } = {\n    3: { src: meh, alt: 'meh', boxSize: '25px' },\n    4: { src: thumbsUp, alt: 'recommended', boxSize: '25px' },\n    5: { src: bullsEye, alt: 'exceptional', boxSize: '35px' },\n  }\n\n  return (\n    <Image {...emojiMap[rating]} marginTop={1} />\n  )\n}\n\nexport default Emoji"
  },
  {
    "path": "src/components/GameCard.tsx",
    "content": "import { Card, CardBody, Heading, HStack, Image, Text } from '@chakra-ui/react'\nimport React from 'react'\nimport { Game } from '../hooks/useGames'\nimport getCroppedImageUrl from '../services/image-url'\nimport CriticScore from './CriticScore'\nimport Emoji from './Emoji'\nimport PlatformIconList from './PlatformIconList'\n\ninterface Props {\n  game: Game\n}\n\nconst GameCard = ({ game }: Props) => {\n  return (\n    <Card>\n      <Image src={getCroppedImageUrl(game.background_image)} />\n      <CardBody>\n        <HStack justifyContent='space-between' marginBottom={3}>\n          <PlatformIconList platforms={game.parent_platforms?.map(p => p.platform)} />\n          <CriticScore score={game.metacritic} />\n        </HStack>\n        <Heading fontSize='2xl'>{game.name}<Emoji rating={game.rating_top}/></Heading>\n      </CardBody>\n    </Card>\n  )\n}\n\nexport default GameCard"
  },
  {
    "path": "src/components/GameCardContainer.tsx",
    "content": "import { Box } from \"@chakra-ui/react\";\nimport { ReactNode } from \"react\";\n\ninterface Props {\n  children: ReactNode;\n}\n\nconst GameCardContainer = ({ children }: Props) => {\n  return (\n    <Box borderRadius={10} overflow=\"hidden\">\n      {children}\n    </Box>\n  );\n};\n\nexport default GameCardContainer;\n"
  },
  {
    "path": "src/components/GameCardSkeleton.tsx",
    "content": "import { Card, CardBody, Skeleton, SkeletonText } from '@chakra-ui/react'\n\nconst GameCardSkeleton = () => {\n  return (\n    <Card>\n      <Skeleton height=\"200px\" />\n      <CardBody>\n        <SkeletonText />\n      </CardBody>\n    </Card>\n  )\n}\n\nexport default GameCardSkeleton"
  },
  {
    "path": "src/components/GameGrid.tsx",
    "content": "import { SimpleGrid, Text } from \"@chakra-ui/react\";\nimport { GameQuery } from \"../App\";\nimport useGames, { Platform } from \"../hooks/useGames\";\nimport { Genre } from \"../hooks/useGenres\";\nimport GameCard from \"./GameCard\";\nimport GameCardContainer from \"./GameCardContainer\";\nimport GameCardSkeleton from \"./GameCardSkeleton\";\n\ninterface Props {\n  gameQuery: GameQuery;\n}\n\nconst GameGrid = ({ gameQuery }: Props) => {\n  const { data, error, isLoading } = useGames(gameQuery);\n  const skeletons = [1, 2, 3, 4, 5, 6];\n\n  if (error) return <Text>{error}</Text>;\n\n  return (\n    <SimpleGrid\n      columns={{ sm: 1, md: 2, lg: 3, xl: 4 }}\n      padding=\"10px\"\n      spacing={6}\n    >\n      {isLoading &&\n        skeletons.map((skeleton) => (\n          <GameCardContainer key={skeleton}>\n            <GameCardSkeleton />\n          </GameCardContainer>\n        ))}\n      {data.map((game) => (\n        <GameCardContainer key={game.id}>\n          <GameCard game={game} />\n        </GameCardContainer>\n      ))}\n    </SimpleGrid>\n  );\n};\n\nexport default GameGrid;\n"
  },
  {
    "path": "src/components/GameHeading.tsx",
    "content": "import { Heading } from '@chakra-ui/react'\nimport { GameQuery } from '../App'\n\ninterface Props {\n  gameQuery: GameQuery\n}\n\nconst GameHeading = ({ gameQuery }: Props) => {\n  const heading = `${gameQuery.platform?.name || ''} ${gameQuery.genre?.name || ''} Games`;\n\n  return (\n    <Heading as='h1' marginY={5} fontSize='5xl'>{heading}</Heading>\n  )\n}\n\nexport default GameHeading"
  },
  {
    "path": "src/components/GenreList.tsx",
    "content": "import {\n  Button,\n  Heading,\n  HStack,\n  Image,\n  List,\n  ListItem,\n  Spinner,\n  Text,\n} from \"@chakra-ui/react\";\nimport useGenres, { Genre } from \"../hooks/useGenres\";\nimport getCroppedImageUrl from \"../services/image-url\";\n\ninterface Props {\n  onSelectGenre: (genre: Genre) => void;\n  selectedGenre: Genre | null;\n}\n\nconst GenreList = ({ selectedGenre, onSelectGenre }: Props) => {\n  const { data, isLoading, error } = useGenres();\n\n  if (error) return null;\n\n  if (isLoading) return <Spinner />;\n\n  return (\n    <>\n      <Heading fontSize=\"2xl\" marginTop={9} marginBottom={3}>\n        Genres\n      </Heading>\n      <List>\n        {data.map((genre) => (\n          <ListItem key={genre.id} paddingY=\"5px\">\n            <HStack>\n              <Image\n                boxSize=\"32px\"\n                borderRadius={8}\n                objectFit=\"cover\"\n                src={getCroppedImageUrl(genre.image_background)}\n              />\n              <Button\n                whiteSpace=\"normal\"\n                textAlign=\"left\"\n                fontWeight={genre.id === selectedGenre?.id ? \"bold\" : \"normal\"}\n                onClick={() => onSelectGenre(genre)}\n                fontSize=\"md\"\n                variant=\"link\"\n              >\n                {genre.name}\n              </Button>\n            </HStack>\n          </ListItem>\n        ))}\n      </List>\n    </>\n  );\n};\n\nexport default GenreList;\n"
  },
  {
    "path": "src/components/NavBar.tsx",
    "content": "import { HStack, Image } from '@chakra-ui/react'\nimport logo from '../assets/logo.webp';\nimport ColorModeSwitch from './ColorModeSwitch';\nimport SearchInput from './SearchInput';\n\ninterface Props {\n  onSearch: (searchText: string) => void;\n}\n\nconst NavBar = ({ onSearch }: Props) => {\n  return (\n    <HStack padding='10px'>\n      <Image src={logo} boxSize='60px' />\n      <SearchInput onSearch={onSearch} />\n      <ColorModeSwitch />\n    </HStack>\n  )\n}\n\nexport default NavBar"
  },
  {
    "path": "src/components/PlatformIconList.tsx",
    "content": "import {\n  FaWindows,\n  FaPlaystation,\n  FaXbox,\n  FaApple,\n  FaLinux,\n  FaAndroid,\n} from \"react-icons/fa\";\nimport { MdPhoneIphone } from 'react-icons/md';\nimport { SiNintendo } from 'react-icons/si';\nimport { BsGlobe } from 'react-icons/bs';\nimport { HStack, Icon } from \"@chakra-ui/react\";\nimport { Platform } from \"../hooks/useGames\";\nimport { IconType } from \"react-icons\";\n\ninterface Props {\n  platforms: Platform[];\n}\n\nconst PlatformIconList = ({ platforms = [] }: Props) => {\n  const iconMap: { [key: string]: IconType } = { \n    pc: FaWindows,\n    playstation: FaPlaystation,\n    xbox: FaXbox,\n    nintendo: SiNintendo,\n    mac: FaApple,\n    linux: FaLinux, \n    android: FaAndroid,\n    ios: MdPhoneIphone,\n    web: BsGlobe\n  }\n\n  return (\n    <HStack marginY={1}> \n      {platforms.map((platform) => (\n        <Icon key={platform.id} as={iconMap[platform.slug]} color='gray.500'/>\n      ))}\n    </HStack>\n  );\n};\n\nexport default PlatformIconList;\n"
  },
  {
    "path": "src/components/PlatformSelector.tsx",
    "content": "import { Button, Menu, MenuButton, MenuItem, MenuList } from \"@chakra-ui/react\";\nimport { BsChevronDown } from \"react-icons/bs\";\nimport { Platform } from \"../hooks/useGames\";\nimport usePlatforms from \"../hooks/usePlatforms\";\n\ninterface Props { \n  onSelectPlatform: (platform: Platform) => void;\n  selectedPlatform: Platform | null;\n}\n\nconst PlatformSelector = ({ onSelectPlatform, selectedPlatform }: Props) => {\n  const { data, error } = usePlatforms();\n\n  if (error) return null;\n  \n  return (\n    <Menu>\n      <MenuButton as={Button} rightIcon={<BsChevronDown />}>\n        {selectedPlatform?.name || 'Platforms'}\n      </MenuButton>\n      <MenuList>\n        {data.map(platform => <MenuItem onClick={() => onSelectPlatform(platform)} key={platform.id}>{platform.name}</MenuItem>)}\n      </MenuList>\n    </Menu>\n  );\n};\n\nexport default PlatformSelector;\n"
  },
  {
    "path": "src/components/SearchInput.tsx",
    "content": "import { Input, InputGroup, InputLeftElement } from \"@chakra-ui/react\";\nimport { useRef } from \"react\";\nimport { BsSearch } from \"react-icons/bs\";\n\ninterface Props {\n  onSearch: (searchText: string) => void;\n}\n\nconst SearchInput = ({ onSearch }: Props) => {\n  const ref = useRef<HTMLInputElement>(null);\n\n  return (\n    <form onSubmit={(event) => {\n      event.preventDefault();\n      if (ref.current) onSearch(ref.current.value);\n    }}>\n      <InputGroup>\n        <InputLeftElement children={<BsSearch />} />\n        <Input ref={ref} borderRadius={20} placeholder=\"Search games...\" variant=\"filled\" />\n      </InputGroup>\n    </form>\n  );\n};\n\nexport default SearchInput;\n"
  },
  {
    "path": "src/components/SortSelector.tsx",
    "content": "import { Button, Menu, MenuButton, MenuItem, MenuList } from \"@chakra-ui/react\";\nimport { BsChevronDown } from \"react-icons/bs\";\n\ninterface Props {\n  onSelectSortOrder: (sortOrder: string) => void;\n  sortOrder: string;\n}\n\nconst SortSelector = ({ onSelectSortOrder, sortOrder }: Props) => {\n  const sortOrders = [\n    { value: \"\", label: \"Relevance\" },\n    { value: \"-added\", label: \"Date added\" },\n    { value: \"name\", label: \"Name\" },\n    { value: \"-released\", label: \"Release date\" },\n    { value: \"-metacritic\", label: \"Popularity\" },\n    { value: \"-rating\", label: \"Average rating\" },\n  ];\n\n  const currentSortOrder = sortOrders.find(order => order.value === sortOrder);\n\n  return (\n    <Menu>\n      <MenuButton as={Button} rightIcon={<BsChevronDown />}>\n        Order by: {currentSortOrder?.label || 'Relevance'}\n      </MenuButton>\n      <MenuList>\n        {sortOrders.map((order) => (\n          <MenuItem onClick={() => onSelectSortOrder(order.value)} key={order.value} value={order.value}>\n            {order.label}\n          </MenuItem>\n        ))}\n      </MenuList>\n    </Menu>\n  );\n};\n\nexport default SortSelector;\n"
  },
  {
    "path": "src/data/genres.ts",
    "content": "export default [\n  {\n    \"id\": 4,\n    \"name\": \"Action\",\n    \"slug\": \"action\",\n    \"games_count\": 177189,\n    \"image_background\": \"https://media.rawg.io/media/games/26d/26d4437715bee60138dab4a7c8c59c92.jpg\",\n    \"games\": [\n      {\n        \"id\": 3498,\n        \"slug\": \"grand-theft-auto-v\",\n        \"name\": \"Grand Theft Auto V\",\n        \"added\": 18995\n      },\n      {\n        \"id\": 3328,\n        \"slug\": \"the-witcher-3-wild-hunt\",\n        \"name\": \"The Witcher 3: Wild Hunt\",\n        \"added\": 18127\n      },\n      {\n        \"id\": 5286,\n        \"slug\": \"tomb-raider\",\n        \"name\": \"Tomb Raider (2013)\",\n        \"added\": 14967\n      },\n      {\n        \"id\": 4291,\n        \"slug\": \"counter-strike-global-offensive\",\n        \"name\": \"Counter-Strike: Global Offensive\",\n        \"added\": 14774\n      },\n      {\n        \"id\": 12020,\n        \"slug\": \"left-4-dead-2\",\n        \"name\": \"Left 4 Dead 2\",\n        \"added\": 14489\n      },\n      {\n        \"id\": 5679,\n        \"slug\": \"the-elder-scrolls-v-skyrim\",\n        \"name\": \"The Elder Scrolls V: Skyrim\",\n        \"added\": 14391\n      }\n    ]\n  },\n  {\n    \"id\": 51,\n    \"name\": \"Indie\",\n    \"slug\": \"indie\",\n    \"games_count\": 51112,\n    \"image_background\": \"https://media.rawg.io/media/games/f46/f466571d536f2e3ea9e815ad17177501.jpg\",\n    \"games\": [\n      {\n        \"id\": 1030,\n        \"slug\": \"limbo\",\n        \"name\": \"Limbo\",\n        \"added\": 12272\n      },\n      {\n        \"id\": 3272,\n        \"slug\": \"rocket-league\",\n        \"name\": \"Rocket League\",\n        \"added\": 11164\n      },\n      {\n        \"id\": 422,\n        \"slug\": \"terraria\",\n        \"name\": \"Terraria\",\n        \"added\": 10975\n      },\n      {\n        \"id\": 9767,\n        \"slug\": \"hollow-knight\",\n        \"name\": \"Hollow Knight\",\n        \"added\": 9542\n      },\n      {\n        \"id\": 3612,\n        \"slug\": \"hotline-miami\",\n        \"name\": \"Hotline Miami\",\n        \"added\": 9376\n      },\n      {\n        \"id\": 3790,\n        \"slug\": \"outlast\",\n        \"name\": \"Outlast\",\n        \"added\": 9296\n      }\n    ]\n  },\n  {\n    \"id\": 3,\n    \"name\": \"Adventure\",\n    \"slug\": \"adventure\",\n    \"games_count\": 136213,\n    \"image_background\": \"https://media.rawg.io/media/games/021/021c4e21a1824d2526f925eff6324653.jpg\",\n    \"games\": [\n      {\n        \"id\": 3498,\n        \"slug\": \"grand-theft-auto-v\",\n        \"name\": \"Grand Theft Auto V\",\n        \"added\": 18995\n      },\n      {\n        \"id\": 3328,\n        \"slug\": \"the-witcher-3-wild-hunt\",\n        \"name\": \"The Witcher 3: Wild Hunt\",\n        \"added\": 18127\n      },\n      {\n        \"id\": 5286,\n        \"slug\": \"tomb-raider\",\n        \"name\": \"Tomb Raider (2013)\",\n        \"added\": 14967\n      },\n      {\n        \"id\": 13536,\n        \"slug\": \"portal\",\n        \"name\": \"Portal\",\n        \"added\": 14517\n      },\n      {\n        \"id\": 28,\n        \"slug\": \"red-dead-redemption-2\",\n        \"name\": \"Red Dead Redemption 2\",\n        \"added\": 13743\n      },\n      {\n        \"id\": 3439,\n        \"slug\": \"life-is-strange-episode-1-2\",\n        \"name\": \"Life is Strange\",\n        \"added\": 13719\n      }\n    ]\n  },\n  {\n    \"id\": 5,\n    \"name\": \"RPG\",\n    \"slug\": \"role-playing-games-rpg\",\n    \"games_count\": 53575,\n    \"image_background\": \"https://media.rawg.io/media/games/f6b/f6bed028b02369d4cab548f4f9337e81.jpg\",\n    \"games\": [\n      {\n        \"id\": 3328,\n        \"slug\": \"the-witcher-3-wild-hunt\",\n        \"name\": \"The Witcher 3: Wild Hunt\",\n        \"added\": 18127\n      },\n      {\n        \"id\": 5679,\n        \"slug\": \"the-elder-scrolls-v-skyrim\",\n        \"name\": \"The Elder Scrolls V: Skyrim\",\n        \"added\": 14391\n      },\n      {\n        \"id\": 802,\n        \"slug\": \"borderlands-2\",\n        \"name\": \"Borderlands 2\",\n        \"added\": 13720\n      },\n      {\n        \"id\": 58175,\n        \"slug\": \"god-of-war-2\",\n        \"name\": \"God of War (2018)\",\n        \"added\": 12068\n      },\n      {\n        \"id\": 3070,\n        \"slug\": \"fallout-4\",\n        \"name\": \"Fallout 4\",\n        \"added\": 12065\n      },\n      {\n        \"id\": 278,\n        \"slug\": \"horizon-zero-dawn\",\n        \"name\": \"Horizon Zero Dawn\",\n        \"added\": 11512\n      }\n    ]\n  },\n  {\n    \"id\": 10,\n    \"name\": \"Strategy\",\n    \"slug\": \"strategy\",\n    \"games_count\": 53393,\n    \"image_background\": \"https://media.rawg.io/media/games/40a/40ab95c1639aa1d7ec04d4cd523af6b1.jpg\",\n    \"games\": [\n      {\n        \"id\": 13633,\n        \"slug\": \"civilization-v\",\n        \"name\": \"Sid Meier's Civilization V\",\n        \"added\": 8490\n      },\n      {\n        \"id\": 10243,\n        \"slug\": \"company-of-heroes-2\",\n        \"name\": \"Company of Heroes 2\",\n        \"added\": 8371\n      },\n      {\n        \"id\": 13910,\n        \"slug\": \"xcom-enemy-unknown\",\n        \"name\": \"XCOM: Enemy Unknown\",\n        \"added\": 7567\n      },\n      {\n        \"id\": 5525,\n        \"slug\": \"brutal-legend\",\n        \"name\": \"Brutal Legend\",\n        \"added\": 7502\n      },\n      {\n        \"id\": 10065,\n        \"slug\": \"cities-skylines\",\n        \"name\": \"Cities: Skylines\",\n        \"added\": 7299\n      },\n      {\n        \"id\": 11147,\n        \"slug\": \"ark-survival-of-the-fittest\",\n        \"name\": \"ARK: Survival Of The Fittest\",\n        \"added\": 6962\n      }\n    ]\n  },\n  {\n    \"id\": 2,\n    \"name\": \"Shooter\",\n    \"slug\": \"shooter\",\n    \"games_count\": 62697,\n    \"image_background\": \"https://media.rawg.io/media/games/120/1201a40e4364557b124392ee50317b99.jpg\",\n    \"games\": [\n      {\n        \"id\": 4200,\n        \"slug\": \"portal-2\",\n        \"name\": \"Portal 2\",\n        \"added\": 17084\n      },\n      {\n        \"id\": 4291,\n        \"slug\": \"counter-strike-global-offensive\",\n        \"name\": \"Counter-Strike: Global Offensive\",\n        \"added\": 14774\n      },\n      {\n        \"id\": 12020,\n        \"slug\": \"left-4-dead-2\",\n        \"name\": \"Left 4 Dead 2\",\n        \"added\": 14489\n      },\n      {\n        \"id\": 4062,\n        \"slug\": \"bioshock-infinite\",\n        \"name\": \"BioShock Infinite\",\n        \"added\": 13894\n      },\n      {\n        \"id\": 802,\n        \"slug\": \"borderlands-2\",\n        \"name\": \"Borderlands 2\",\n        \"added\": 13720\n      },\n      {\n        \"id\": 13537,\n        \"slug\": \"half-life-2\",\n        \"name\": \"Half-Life 2\",\n        \"added\": 12970\n      }\n    ]\n  },\n  {\n    \"id\": 40,\n    \"name\": \"Casual\",\n    \"slug\": \"casual\",\n    \"games_count\": 43416,\n    \"image_background\": \"https://media.rawg.io/media/screenshots/42d/42d770eb49f2ba01cd4045e0d92af7a9.jpg\",\n    \"games\": [\n      {\n        \"id\": 9721,\n        \"slug\": \"garrys-mod\",\n        \"name\": \"Garry's Mod\",\n        \"added\": 8560\n      },\n      {\n        \"id\": 326292,\n        \"slug\": \"fall-guys\",\n        \"name\": \"Fall Guys: Ultimate Knockout\",\n        \"added\": 7532\n      },\n      {\n        \"id\": 9830,\n        \"slug\": \"brawlhalla\",\n        \"name\": \"Brawlhalla\",\n        \"added\": 6465\n      },\n      {\n        \"id\": 356714,\n        \"slug\": \"among-us\",\n        \"name\": \"Among Us\",\n        \"added\": 6137\n      },\n      {\n        \"id\": 1959,\n        \"slug\": \"goat-simulator\",\n        \"name\": \"Goat Simulator\",\n        \"added\": 5693\n      },\n      {\n        \"id\": 16343,\n        \"slug\": \"a-story-about-my-uncle\",\n        \"name\": \"A Story About My Uncle\",\n        \"added\": 5377\n      }\n    ]\n  },\n  {\n    \"id\": 14,\n    \"name\": \"Simulation\",\n    \"slug\": \"simulation\",\n    \"games_count\": 66761,\n    \"image_background\": \"https://media.rawg.io/media/games/997/997ab4d67e96fb20a4092383477d4463.jpg\",\n    \"games\": [\n      {\n        \"id\": 10035,\n        \"slug\": \"hitman\",\n        \"name\": \"Hitman\",\n        \"added\": 9640\n      },\n      {\n        \"id\": 654,\n        \"slug\": \"stardew-valley\",\n        \"name\": \"Stardew Valley\",\n        \"added\": 8632\n      },\n      {\n        \"id\": 9721,\n        \"slug\": \"garrys-mod\",\n        \"name\": \"Garry's Mod\",\n        \"added\": 8560\n      },\n      {\n        \"id\": 10243,\n        \"slug\": \"company-of-heroes-2\",\n        \"name\": \"Company of Heroes 2\",\n        \"added\": 8371\n      },\n      {\n        \"id\": 9882,\n        \"slug\": \"dont-starve-together\",\n        \"name\": \"Don't Starve Together\",\n        \"added\": 8007\n      },\n      {\n        \"id\": 22509,\n        \"slug\": \"minecraft\",\n        \"name\": \"Minecraft\",\n        \"added\": 7333\n      }\n    ]\n  },\n  {\n    \"id\": 7,\n    \"name\": \"Puzzle\",\n    \"slug\": \"puzzle\",\n    \"games_count\": 99805,\n    \"image_background\": \"https://media.rawg.io/media/games/8cd/8cd179c85bd3de8f79bef245b15075fb.jpg\",\n    \"games\": [\n      {\n        \"id\": 4200,\n        \"slug\": \"portal-2\",\n        \"name\": \"Portal 2\",\n        \"added\": 17084\n      },\n      {\n        \"id\": 13536,\n        \"slug\": \"portal\",\n        \"name\": \"Portal\",\n        \"added\": 14517\n      },\n      {\n        \"id\": 1030,\n        \"slug\": \"limbo\",\n        \"name\": \"Limbo\",\n        \"added\": 12272\n      },\n      {\n        \"id\": 19709,\n        \"slug\": \"half-life-2-episode-two\",\n        \"name\": \"Half-Life 2: Episode Two\",\n        \"added\": 9682\n      },\n      {\n        \"id\": 18080,\n        \"slug\": \"half-life\",\n        \"name\": \"Half-Life\",\n        \"added\": 8892\n      },\n      {\n        \"id\": 1450,\n        \"slug\": \"inside\",\n        \"name\": \"INSIDE\",\n        \"added\": 7112\n      }\n    ]\n  },\n  {\n    \"id\": 11,\n    \"name\": \"Arcade\",\n    \"slug\": \"arcade\",\n    \"games_count\": 22544,\n    \"image_background\": \"https://media.rawg.io/media/games/238/238e2b2b24c9838626700c69cacf1e3a.jpg\",\n    \"games\": [\n      {\n        \"id\": 3612,\n        \"slug\": \"hotline-miami\",\n        \"name\": \"Hotline Miami\",\n        \"added\": 9376\n      },\n      {\n        \"id\": 17540,\n        \"slug\": \"injustice-gods-among-us-ultimate-edition\",\n        \"name\": \"Injustice: Gods Among Us Ultimate Edition\",\n        \"added\": 8582\n      },\n      {\n        \"id\": 22509,\n        \"slug\": \"minecraft\",\n        \"name\": \"Minecraft\",\n        \"added\": 7333\n      },\n      {\n        \"id\": 4003,\n        \"slug\": \"grid-2\",\n        \"name\": \"GRID 2\",\n        \"added\": 6775\n      },\n      {\n        \"id\": 3408,\n        \"slug\": \"hotline-miami-2-wrong-number\",\n        \"name\": \"Hotline Miami 2: Wrong Number\",\n        \"added\": 5476\n      },\n      {\n        \"id\": 16343,\n        \"slug\": \"a-story-about-my-uncle\",\n        \"name\": \"A Story About My Uncle\",\n        \"added\": 5377\n      }\n    ]\n  },\n  {\n    \"id\": 83,\n    \"name\": \"Platformer\",\n    \"slug\": \"platformer\",\n    \"games_count\": 105652,\n    \"image_background\": \"https://media.rawg.io/media/games/fd7/fd794a9f0ffe816038d981b3acc3eec9.jpg\",\n    \"games\": [\n      {\n        \"id\": 1030,\n        \"slug\": \"limbo\",\n        \"name\": \"Limbo\",\n        \"added\": 12272\n      },\n      {\n        \"id\": 422,\n        \"slug\": \"terraria\",\n        \"name\": \"Terraria\",\n        \"added\": 10975\n      },\n      {\n        \"id\": 9767,\n        \"slug\": \"hollow-knight\",\n        \"name\": \"Hollow Knight\",\n        \"added\": 9542\n      },\n      {\n        \"id\": 41,\n        \"slug\": \"little-nightmares\",\n        \"name\": \"Little Nightmares\",\n        \"added\": 9422\n      },\n      {\n        \"id\": 18080,\n        \"slug\": \"half-life\",\n        \"name\": \"Half-Life\",\n        \"added\": 8892\n      },\n      {\n        \"id\": 3144,\n        \"slug\": \"super-meat-boy\",\n        \"name\": \"Super Meat Boy\",\n        \"added\": 8559\n      }\n    ]\n  },\n  {\n    \"id\": 1,\n    \"name\": \"Racing\",\n    \"slug\": \"racing\",\n    \"games_count\": 24448,\n    \"image_background\": \"https://media.rawg.io/media/games/23d/23d78acedbb5f40c9fb64e73af5af65d.jpg\",\n    \"games\": [\n      {\n        \"id\": 3272,\n        \"slug\": \"rocket-league\",\n        \"name\": \"Rocket League\",\n        \"added\": 11164\n      },\n      {\n        \"id\": 4003,\n        \"slug\": \"grid-2\",\n        \"name\": \"GRID 2\",\n        \"added\": 6775\n      },\n      {\n        \"id\": 2572,\n        \"slug\": \"dirt-rally\",\n        \"name\": \"DiRT Rally\",\n        \"added\": 6068\n      },\n      {\n        \"id\": 58753,\n        \"slug\": \"forza-horizon-4\",\n        \"name\": \"Forza Horizon 4\",\n        \"added\": 5375\n      },\n      {\n        \"id\": 5578,\n        \"slug\": \"grid\",\n        \"name\": \"Race Driver: Grid\",\n        \"added\": 4981\n      },\n      {\n        \"id\": 4347,\n        \"slug\": \"dirt-showdown\",\n        \"name\": \"DiRT Showdown\",\n        \"added\": 4332\n      }\n    ]\n  },\n  {\n    \"id\": 59,\n    \"name\": \"Massively Multiplayer\",\n    \"slug\": \"massively-multiplayer\",\n    \"games_count\": 3152,\n    \"image_background\": \"https://media.rawg.io/media/games/651/651ae84f2d5e36206aad90976a453329.jpg\",\n    \"games\": [\n      {\n        \"id\": 32,\n        \"slug\": \"destiny-2\",\n        \"name\": \"Destiny 2\",\n        \"added\": 12059\n      },\n      {\n        \"id\": 10213,\n        \"slug\": \"dota-2\",\n        \"name\": \"Dota 2\",\n        \"added\": 11021\n      },\n      {\n        \"id\": 766,\n        \"slug\": \"warframe\",\n        \"name\": \"Warframe\",\n        \"added\": 10860\n      },\n      {\n        \"id\": 290856,\n        \"slug\": \"apex-legends\",\n        \"name\": \"Apex Legends\",\n        \"added\": 9605\n      },\n      {\n        \"id\": 10533,\n        \"slug\": \"path-of-exile\",\n        \"name\": \"Path of Exile\",\n        \"added\": 8685\n      },\n      {\n        \"id\": 10142,\n        \"slug\": \"playerunknowns-battlegrounds\",\n        \"name\": \"PlayerUnknown’s Battlegrounds\",\n        \"added\": 8537\n      }\n    ]\n  },\n  {\n    \"id\": 15,\n    \"name\": \"Sports\",\n    \"slug\": \"sports\",\n    \"games_count\": 20711,\n    \"image_background\": \"https://media.rawg.io/media/screenshots/f5a/f5abab52c4d606551cd5ec3ab709e501.jpg\",\n    \"games\": [\n      {\n        \"id\": 3272,\n        \"slug\": \"rocket-league\",\n        \"name\": \"Rocket League\",\n        \"added\": 11164\n      },\n      {\n        \"id\": 326292,\n        \"slug\": \"fall-guys\",\n        \"name\": \"Fall Guys: Ultimate Knockout\",\n        \"added\": 7532\n      },\n      {\n        \"id\": 2572,\n        \"slug\": \"dirt-rally\",\n        \"name\": \"DiRT Rally\",\n        \"added\": 6068\n      },\n      {\n        \"id\": 53341,\n        \"slug\": \"jet-set-radio-2012\",\n        \"name\": \"Jet Set Radio\",\n        \"added\": 4740\n      },\n      {\n        \"id\": 9575,\n        \"slug\": \"vrchat\",\n        \"name\": \"VRChat\",\n        \"added\": 3909\n      },\n      {\n        \"id\": 622492,\n        \"slug\": \"forza-horizon-5\",\n        \"name\": \"Forza Horizon 5\",\n        \"added\": 3873\n      }\n    ]\n  },\n  {\n    \"id\": 6,\n    \"name\": \"Fighting\",\n    \"slug\": \"fighting\",\n    \"games_count\": 12308,\n    \"image_background\": \"https://media.rawg.io/media/games/cc5/cc576aa274780702ef93463f5410031e.jpg\",\n    \"games\": [\n      {\n        \"id\": 17540,\n        \"slug\": \"injustice-gods-among-us-ultimate-edition\",\n        \"name\": \"Injustice: Gods Among Us Ultimate Edition\",\n        \"added\": 8582\n      },\n      {\n        \"id\": 108,\n        \"slug\": \"mortal-kombat-x\",\n        \"name\": \"Mortal Kombat X\",\n        \"added\": 7908\n      },\n      {\n        \"id\": 28179,\n        \"slug\": \"sega-mega-drive-and-genesis-classics\",\n        \"name\": \"SEGA Mega Drive and Genesis Classics\",\n        \"added\": 7280\n      },\n      {\n        \"id\": 9830,\n        \"slug\": \"brawlhalla\",\n        \"name\": \"Brawlhalla\",\n        \"added\": 6465\n      },\n      {\n        \"id\": 274480,\n        \"slug\": \"mortal-kombat-11\",\n        \"name\": \"Mortal Kombat 11\",\n        \"added\": 4685\n      },\n      {\n        \"id\": 44525,\n        \"slug\": \"yakuza-kiwami\",\n        \"name\": \"Yakuza Kiwami\",\n        \"added\": 4038\n      }\n    ]\n  },\n  {\n    \"id\": 19,\n    \"name\": \"Family\",\n    \"slug\": \"family\",\n    \"games_count\": 5378,\n    \"image_background\": \"https://media.rawg.io/media/games/9f7/9f750cb69a31a42648f45e3681abed3a.jpg\",\n    \"games\": [\n      {\n        \"id\": 3254,\n        \"slug\": \"journey\",\n        \"name\": \"Journey\",\n        \"added\": 7784\n      },\n      {\n        \"id\": 2597,\n        \"slug\": \"lego-lord-of-the-rings\",\n        \"name\": \"LEGO The Lord of the Rings\",\n        \"added\": 4950\n      },\n      {\n        \"id\": 3350,\n        \"slug\": \"broken-age\",\n        \"name\": \"Broken Age\",\n        \"added\": 4625\n      },\n      {\n        \"id\": 3729,\n        \"slug\": \"lego-the-hobbit\",\n        \"name\": \"LEGO The Hobbit\",\n        \"added\": 4549\n      },\n      {\n        \"id\": 1259,\n        \"slug\": \"machinarium\",\n        \"name\": \"Machinarium\",\n        \"added\": 4105\n      },\n      {\n        \"id\": 1140,\n        \"slug\": \"world-of-goo\",\n        \"name\": \"World of Goo\",\n        \"added\": 4042\n      }\n    ]\n  },\n  {\n    \"id\": 28,\n    \"name\": \"Board Games\",\n    \"slug\": \"board-games\",\n    \"games_count\": 8272,\n    \"image_background\": \"https://media.rawg.io/media/screenshots/768/768e36d4b62a1481fef737c6920fbfc7.jpg\",\n    \"games\": [\n      {\n        \"id\": 23557,\n        \"slug\": \"gwent-the-witcher-card-game\",\n        \"name\": \"Gwent: The Witcher Card Game\",\n        \"added\": 4218\n      },\n      {\n        \"id\": 327999,\n        \"slug\": \"dota-underlords\",\n        \"name\": \"Dota Underlords\",\n        \"added\": 3557\n      },\n      {\n        \"id\": 2055,\n        \"slug\": \"adventure-capitalist\",\n        \"name\": \"AdVenture Capitalist\",\n        \"added\": 2972\n      },\n      {\n        \"id\": 2306,\n        \"slug\": \"poker-night-2\",\n        \"name\": \"Poker Night 2\",\n        \"added\": 1914\n      },\n      {\n        \"id\": 3187,\n        \"slug\": \"armello\",\n        \"name\": \"Armello\",\n        \"added\": 1802\n      },\n      {\n        \"id\": 758,\n        \"slug\": \"hue\",\n        \"name\": \"Hue\",\n        \"added\": 1743\n      }\n    ]\n  },\n  {\n    \"id\": 34,\n    \"name\": \"Educational\",\n    \"slug\": \"educational\",\n    \"games_count\": 16155,\n    \"image_background\": \"https://media.rawg.io/media/screenshots/6cd/6cd13ed3dcb6b44b8bc995850f2861e6.jpg\",\n    \"games\": [\n      {\n        \"id\": 1358,\n        \"slug\": \"papers-please\",\n        \"name\": \"Papers, Please\",\n        \"added\": 6053\n      },\n      {\n        \"id\": 1140,\n        \"slug\": \"world-of-goo\",\n        \"name\": \"World of Goo\",\n        \"added\": 4042\n      },\n      {\n        \"id\": 2778,\n        \"slug\": \"surgeon-simulator-cpr\",\n        \"name\": \"Surgeon Simulator\",\n        \"added\": 3545\n      },\n      {\n        \"id\": 9768,\n        \"slug\": \"gameguru\",\n        \"name\": \"GameGuru\",\n        \"added\": 2260\n      },\n      {\n        \"id\": 13777,\n        \"slug\": \"sid-meiers-civilization-iv-colonization\",\n        \"name\": \"Sid Meier's Civilization IV: Colonization\",\n        \"added\": 2105\n      },\n      {\n        \"id\": 6885,\n        \"slug\": \"pirates-3\",\n        \"name\": \"Sid Meier's Pirates!\",\n        \"added\": 1995\n      }\n    ]\n  },\n  {\n    \"id\": 17,\n    \"name\": \"Card\",\n    \"slug\": \"card\",\n    \"games_count\": 4461,\n    \"image_background\": \"https://media.rawg.io/media/screenshots/ca2/ca257e3481af0b2c8149d6600440aa85.jpeg\",\n    \"games\": [\n      {\n        \"id\": 23557,\n        \"slug\": \"gwent-the-witcher-card-game\",\n        \"name\": \"Gwent: The Witcher Card Game\",\n        \"added\": 4218\n      },\n      {\n        \"id\": 28121,\n        \"slug\": \"slay-the-spire\",\n        \"name\": \"Slay the Spire\",\n        \"added\": 4182\n      },\n      {\n        \"id\": 18852,\n        \"slug\": \"poker-night-at-the-inventory\",\n        \"name\": \"Poker Night at the Inventory\",\n        \"added\": 2539\n      },\n      {\n        \"id\": 8923,\n        \"slug\": \"faeria\",\n        \"name\": \"Faeria\",\n        \"added\": 1998\n      },\n      {\n        \"id\": 332,\n        \"slug\": \"the-elder-scrolls-legends\",\n        \"name\": \"The Elder Scrolls: Legends\",\n        \"added\": 1932\n      },\n      {\n        \"id\": 2306,\n        \"slug\": \"poker-night-2\",\n        \"name\": \"Poker Night 2\",\n        \"added\": 1914\n      }\n    ]\n  }\n];"
  },
  {
    "path": "src/data/platforms.ts",
    "content": "export default [\n  {\n    \"id\": 1,\n    \"name\": \"PC\",\n    \"slug\": \"pc\",\n    \"platforms\": [\n      {\n        \"id\": 4,\n        \"name\": \"PC\",\n        \"slug\": \"pc\",\n        \"games_count\": 534727,\n        \"image_background\": \"https://media.rawg.io/media/games/26d/26d4437715bee60138dab4a7c8c59c92.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 2,\n    \"name\": \"PlayStation\",\n    \"slug\": \"playstation\",\n    \"platforms\": [\n      {\n        \"id\": 187,\n        \"name\": \"PlayStation 5\",\n        \"slug\": \"playstation5\",\n        \"games_count\": 819,\n        \"image_background\": \"https://media.rawg.io/media/games/d89/d89bd0cf4fcdc10820892980cbba0f49.jpg\",\n        \"image\": null,\n        \"year_start\": 2020,\n        \"year_end\": null\n      },\n      {\n        \"id\": 18,\n        \"name\": \"PlayStation 4\",\n        \"slug\": \"playstation4\",\n        \"games_count\": 6590,\n        \"image_background\": \"https://media.rawg.io/media/games/c4b/c4b0cab189e73432de3a250d8cf1c84e.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 16,\n        \"name\": \"PlayStation 3\",\n        \"slug\": \"playstation3\",\n        \"games_count\": 3323,\n        \"image_background\": \"https://media.rawg.io/media/games/456/456dea5e1c7e3cd07060c14e96612001.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 15,\n        \"name\": \"PlayStation 2\",\n        \"slug\": \"playstation2\",\n        \"games_count\": 1970,\n        \"image_background\": \"https://media.rawg.io/media/games/683/6833fbb183fd72a61c032501e3bc6d36.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 27,\n        \"name\": \"PlayStation\",\n        \"slug\": \"playstation1\",\n        \"games_count\": 1606,\n        \"image_background\": \"https://media.rawg.io/media/games/0c5/0c5fcdf04048200da14b90e0e6cfaf6b.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 19,\n        \"name\": \"PS Vita\",\n        \"slug\": \"ps-vita\",\n        \"games_count\": 1667,\n        \"image_background\": \"https://media.rawg.io/media/games/be0/be084b850302abe81675bc4ffc08a0d0.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 17,\n        \"name\": \"PSP\",\n        \"slug\": \"psp\",\n        \"games_count\": 1438,\n        \"image_background\": \"https://media.rawg.io/media/games/a6c/a6cd31267a20a615d35f618e766208fc.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 3,\n    \"name\": \"Xbox\",\n    \"slug\": \"xbox\",\n    \"platforms\": [\n      {\n        \"id\": 1,\n        \"name\": \"Xbox One\",\n        \"slug\": \"xbox-one\",\n        \"games_count\": 5485,\n        \"image_background\": \"https://media.rawg.io/media/games/f46/f466571d536f2e3ea9e815ad17177501.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 186,\n        \"name\": \"Xbox Series S/X\",\n        \"slug\": \"xbox-series-x\",\n        \"games_count\": 730,\n        \"image_background\": \"https://media.rawg.io/media/games/d47/d479582ed0a46496ad34f65c7099d7e5.jpg\",\n        \"image\": null,\n        \"year_start\": 2020,\n        \"year_end\": null\n      },\n      {\n        \"id\": 14,\n        \"name\": \"Xbox 360\",\n        \"slug\": \"xbox360\",\n        \"games_count\": 2780,\n        \"image_background\": \"https://media.rawg.io/media/games/942/9424d6bb763dc38d9378b488603c87fa.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 80,\n        \"name\": \"Xbox\",\n        \"slug\": \"xbox-old\",\n        \"games_count\": 722,\n        \"image_background\": \"https://media.rawg.io/media/games/bc7/bc77b1eb8e35df2d90b952bac5342c75.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 4,\n    \"name\": \"iOS\",\n    \"slug\": \"ios\",\n    \"platforms\": [\n      {\n        \"id\": 3,\n        \"name\": \"iOS\",\n        \"slug\": \"ios\",\n        \"games_count\": 76450,\n        \"image_background\": \"https://media.rawg.io/media/games/8d6/8d69eb6c32ed6acfd75f82d532144993.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 8,\n    \"name\": \"Android\",\n    \"slug\": \"android\",\n    \"platforms\": [\n      {\n        \"id\": 21,\n        \"name\": \"Android\",\n        \"slug\": \"android\",\n        \"games_count\": 54864,\n        \"image_background\": \"https://media.rawg.io/media/games/e74/e74458058b35e01c1ae3feeb39a3f724.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 5,\n    \"name\": \"Apple Macintosh\",\n    \"slug\": \"mac\",\n    \"platforms\": [\n      {\n        \"id\": 5,\n        \"name\": \"macOS\",\n        \"slug\": \"macos\",\n        \"games_count\": 106165,\n        \"image_background\": \"https://media.rawg.io/media/games/7fa/7fa0b586293c5861ee32490e953a4996.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 55,\n        \"name\": \"Classic Macintosh\",\n        \"slug\": \"macintosh\",\n        \"games_count\": 677,\n        \"image_background\": \"https://media.rawg.io/media/games/dd7/dd72d8a527cd9245c7eb7cd05aa53efa.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 41,\n        \"name\": \"Apple II\",\n        \"slug\": \"apple-ii\",\n        \"games_count\": 422,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/510/510ad66178757fcafc467d6d01c3b425.jpeg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 6,\n    \"name\": \"Linux\",\n    \"slug\": \"linux\",\n    \"platforms\": [\n      {\n        \"id\": 6,\n        \"name\": \"Linux\",\n        \"slug\": \"linux\",\n        \"games_count\": 78912,\n        \"image_background\": \"https://media.rawg.io/media/games/f46/f466571d536f2e3ea9e815ad17177501.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 7,\n    \"name\": \"Nintendo\",\n    \"slug\": \"nintendo\",\n    \"platforms\": [\n      {\n        \"id\": 7,\n        \"name\": \"Nintendo Switch\",\n        \"slug\": \"nintendo-switch\",\n        \"games_count\": 5190,\n        \"image_background\": \"https://media.rawg.io/media/games/fc1/fc1307a2774506b5bd65d7e8424664a7.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 8,\n        \"name\": \"Nintendo 3DS\",\n        \"slug\": \"nintendo-3ds\",\n        \"games_count\": 1731,\n        \"image_background\": \"https://media.rawg.io/media/games/369/36914d895c20e35f273286145c267764.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 9,\n        \"name\": \"Nintendo DS\",\n        \"slug\": \"nintendo-ds\",\n        \"games_count\": 2476,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/157/1571cdfb52888191eabaf53c2b897240.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 13,\n        \"name\": \"Nintendo DSi\",\n        \"slug\": \"nintendo-dsi\",\n        \"games_count\": 37,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/b45/b452e9b20e969a64d0088ae467d1dcab.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 10,\n        \"name\": \"Wii U\",\n        \"slug\": \"wii-u\",\n        \"games_count\": 1203,\n        \"image_background\": \"https://media.rawg.io/media/games/849/849414b978db37d4563ff9e4b0d3a787.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 11,\n        \"name\": \"Wii\",\n        \"slug\": \"wii\",\n        \"games_count\": 2269,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/f10/f10e7cafed6665861c58187b2ae3b310.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 105,\n        \"name\": \"GameCube\",\n        \"slug\": \"gamecube\",\n        \"games_count\": 642,\n        \"image_background\": \"https://media.rawg.io/media/games/4f5/4f57124f7c0285150626cd1411c45b6e.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 83,\n        \"name\": \"Nintendo 64\",\n        \"slug\": \"nintendo-64\",\n        \"games_count\": 364,\n        \"image_background\": \"https://media.rawg.io/media/games/f62/f62d090119e5ff05c59036480123fd83.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 24,\n        \"name\": \"Game Boy Advance\",\n        \"slug\": \"game-boy-advance\",\n        \"games_count\": 956,\n        \"image_background\": \"https://media.rawg.io/media/games/dc6/dc68ca77e06ad993aade7faf645f5ec2.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 43,\n        \"name\": \"Game Boy Color\",\n        \"slug\": \"game-boy-color\",\n        \"games_count\": 414,\n        \"image_background\": \"https://media.rawg.io/media/games/a9a/a9a2472f862b041d2980103ddbb61c91.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 26,\n        \"name\": \"Game Boy\",\n        \"slug\": \"game-boy\",\n        \"games_count\": 604,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/1e5/1e5e083780bb330479f7c778e6a0b7f0.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 79,\n        \"name\": \"SNES\",\n        \"slug\": \"snes\",\n        \"games_count\": 940,\n        \"image_background\": \"https://media.rawg.io/media/games/0df/0dfe8852fa43d58cbdeb973765a9828d.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 49,\n        \"name\": \"NES\",\n        \"slug\": \"nes\",\n        \"games_count\": 970,\n        \"image_background\": \"https://media.rawg.io/media/games/98e/98e3ce9d1be0f578d120168fb6c1e0a0.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 9,\n    \"name\": \"Atari\",\n    \"slug\": \"atari\",\n    \"platforms\": [\n      {\n        \"id\": 28,\n        \"name\": \"Atari 7800\",\n        \"slug\": \"atari-7800\",\n        \"games_count\": 64,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/565/56504b28b184dbc630a7de118e39d822.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 31,\n        \"name\": \"Atari 5200\",\n        \"slug\": \"atari-5200\",\n        \"games_count\": 64,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/61a/61a60e3ee55941387681eaa59e3becbf.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 23,\n        \"name\": \"Atari 2600\",\n        \"slug\": \"atari-2600\",\n        \"games_count\": 286,\n        \"image_background\": \"https://media.rawg.io/media/games/23e/23eecccb588a4a9c97f35ebf8f9f00ef.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 22,\n        \"name\": \"Atari Flashback\",\n        \"slug\": \"atari-flashback\",\n        \"games_count\": 30,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/2aa/2aa07f58491e14b0183333f8956bc802.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 25,\n        \"name\": \"Atari 8-bit\",\n        \"slug\": \"atari-8-bit\",\n        \"games_count\": 306,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/b12/b12ed274eed80e4aced37badf228d1cf.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 34,\n        \"name\": \"Atari ST\",\n        \"slug\": \"atari-st\",\n        \"games_count\": 834,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/f7a/f7a70f1b271de9b92a9714db33e4c8ba.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 46,\n        \"name\": \"Atari Lynx\",\n        \"slug\": \"atari-lynx\",\n        \"games_count\": 56,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/575/575b2838392ed177dd7d2c734c682f93.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 50,\n        \"name\": \"Atari XEGS\",\n        \"slug\": \"atari-xegs\",\n        \"games_count\": 22,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/769/7691726d70c23c029903df08858df001.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 112,\n        \"name\": \"Jaguar\",\n        \"slug\": \"jaguar\",\n        \"games_count\": 37,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/7dd/7dd630a9b38257450b53099932d3047d.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 10,\n    \"name\": \"Commodore / Amiga\",\n    \"slug\": \"commodore-amiga\",\n    \"platforms\": [\n      {\n        \"id\": 166,\n        \"name\": \"Commodore / Amiga\",\n        \"slug\": \"commodore-amiga\",\n        \"games_count\": 2061,\n        \"image_background\": \"https://media.rawg.io/media/games/a9a/a9a2472f862b041d2980103ddbb61c91.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 11,\n    \"name\": \"SEGA\",\n    \"slug\": \"sega\",\n    \"platforms\": [\n      {\n        \"id\": 167,\n        \"name\": \"Genesis\",\n        \"slug\": \"genesis\",\n        \"games_count\": 824,\n        \"image_background\": \"https://media.rawg.io/media/games/373/373a9a1f664de6e4c31f08644729e2db.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 107,\n        \"name\": \"SEGA Saturn\",\n        \"slug\": \"sega-saturn\",\n        \"games_count\": 347,\n        \"image_background\": \"https://media.rawg.io/media/games/47b/47b50d880be8453bf9cda6e5c007bc26.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 119,\n        \"name\": \"SEGA CD\",\n        \"slug\": \"sega-cd\",\n        \"games_count\": 161,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/b45/b452e9b20e969a64d0088ae467d1dcab.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 117,\n        \"name\": \"SEGA 32X\",\n        \"slug\": \"sega-32x\",\n        \"games_count\": 47,\n        \"image_background\": \"https://media.rawg.io/media/games/0df/0dfe8852fa43d58cbdeb973765a9828d.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 74,\n        \"name\": \"SEGA Master System\",\n        \"slug\": \"sega-master-system\",\n        \"games_count\": 223,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/f9a/f9ac59bb4af2ca2193ee9ffb979577cf.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 106,\n        \"name\": \"Dreamcast\",\n        \"slug\": \"dreamcast\",\n        \"games_count\": 353,\n        \"image_background\": \"https://media.rawg.io/media/games/1cf/1cf9e301f1d27172546dcabc2f6cb597.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      },\n      {\n        \"id\": 77,\n        \"name\": \"Game Gear\",\n        \"slug\": \"game-gear\",\n        \"games_count\": 217,\n        \"image_background\": \"https://media.rawg.io/media/games/2c3/2c3363eb1ae202b9e4e7520d3f14ab2e.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 12,\n    \"name\": \"3DO\",\n    \"slug\": \"3do\",\n    \"platforms\": [\n      {\n        \"id\": 111,\n        \"name\": \"3DO\",\n        \"slug\": \"3do\",\n        \"games_count\": 95,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/180/180b5f6e5d8c770bbbf941b9875046b6.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 13,\n    \"name\": \"Neo Geo\",\n    \"slug\": \"neo-geo\",\n    \"platforms\": [\n      {\n        \"id\": 12,\n        \"name\": \"Neo Geo\",\n        \"slug\": \"neogeo\",\n        \"games_count\": 113,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/488/488788e787a69d5ecf3c74884548ec24.jpg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  },\n  {\n    \"id\": 14,\n    \"name\": \"Web\",\n    \"slug\": \"web\",\n    \"platforms\": [\n      {\n        \"id\": 171,\n        \"name\": \"Web\",\n        \"slug\": \"web\",\n        \"games_count\": 269156,\n        \"image_background\": \"https://media.rawg.io/media/screenshots/78d/78d2dc36ce3b03af2e3000a078da8185.jpeg\",\n        \"image\": null,\n        \"year_start\": null,\n        \"year_end\": null\n      }\n    ]\n  }\n]"
  },
  {
    "path": "src/hooks/useData.ts",
    "content": "import { AxiosRequestConfig, CanceledError } from \"axios\";\nimport { useEffect, useState } from \"react\";\nimport apiClient from \"../services/api-client\";\n\ninterface FetchResponse<T> {\n  count: number;\n  results: T[];\n}\n\nconst useData = <T>(endpoint: string, requestConfig?: AxiosRequestConfig, deps?: any[]) => {\n  const [data, setData] = useState<T[]>([]);\n  const [error, setError] = useState(\"\");\n  const [isLoading, setLoading] = useState(false);\n\n  useEffect(() => {\n    const controller = new AbortController();\n\n    setLoading(true);\n    apiClient\n      .get<FetchResponse<T>>(endpoint, { signal: controller.signal, ...requestConfig })\n      .then((res) => {\n        setData(res.data.results);\n        setLoading(false);\n      })\n      .catch((err) => {\n        if (err instanceof CanceledError) return;\n        setError(err.message)\n        setLoading(false);\n      });\n\n    return () => controller.abort();\n  }, deps ? [...deps] : []);\n\n  return { data, error, isLoading };\n};\n\nexport default useData;"
  },
  {
    "path": "src/hooks/useGames.ts",
    "content": "import { GameQuery } from \"../App\";\nimport useData from \"./useData\";\nimport { Genre } from \"./useGenres\";\n\nexport interface Platform {\n  id: number;\n  name: string;\n  slug: string;\n}\n\nexport interface Game {\n  id: number;\n  name: string;\n  background_image: string;\n  parent_platforms: { platform: Platform }[];\n  metacritic: number;\n  rating_top: number;\n}\n\nconst useGames = (gameQuery: GameQuery) =>\n  useData<Game>(\n    \"/games\",\n    {\n      params: {\n        genres: gameQuery.genre?.id,\n        platforms: gameQuery.platform?.id,\n        ordering: gameQuery.sortOrder,\n        search: gameQuery.searchText\n      },\n    },\n    [gameQuery]\n  );\n\nexport default useGames;\n"
  },
  {
    "path": "src/hooks/useGenres.ts",
    "content": "import genres from \"../data/genres\";\n\nexport interface Genre {\n  id: number;\n  name: string;\n  image_background: string;\n}\n\nconst useGenres = () => ({ data: genres, isLoading: false, error: null })\n\nexport default useGenres;"
  },
  {
    "path": "src/hooks/usePlatforms.ts",
    "content": "import platforms from \"../data/platforms\";\n\ninterface Platform {\n  id: number;\n  name: string;\n  slug: string;\n}\n\nconst usePlatforms = () => ({ data: platforms, isLoading: false, error: null });\n\nexport default usePlatforms;\n"
  },
  {
    "path": "src/index.css",
    "content": "form {\n  width: 100%;\n}"
  },
  {
    "path": "src/main.tsx",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport { ChakraProvider, ColorModeScript } from '@chakra-ui/react'\nimport App from './App'\nimport theme from './theme'\nimport './index.css'\n\nReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(\n  <React.StrictMode>\n    <ChakraProvider theme={theme}>\n      <ColorModeScript initialColorMode={theme.config.initialColorMode}/>\n      <App />\n    </ChakraProvider>\n  </React.StrictMode>,\n)\n"
  },
  {
    "path": "src/services/api-client.ts",
    "content": "import axios from \"axios\";\n\nexport default axios.create({\n  baseURL: \"https://api.rawg.io/api\",\n  params: {\n    key: \"c7b18323a47d40c394ea5b019646b1f5\",\n  },\n});\n"
  },
  {
    "path": "src/services/image-url.ts",
    "content": "import noImage from '../assets/no-image-placeholder.webp';\n\nconst getCroppedImageUrl = (url: string) => {\n  if (!url) return noImage;\n  \n  const target = 'media/';\n  const index = url.indexOf(target) + target.length;\n  return url.slice(0, index) + 'crop/600/400/' + url.slice(index);\n}\n\nexport default getCroppedImageUrl;"
  },
  {
    "path": "src/theme.ts",
    "content": "import { extendTheme, ThemeConfig } from \"@chakra-ui/react\";\n\nconst config: ThemeConfig = {\n  initialColorMode: 'dark'\n};\n\nconst theme = extendTheme({ \n  config,\n  colors: {\n    gray: {\n      50: '#f9f9f9',\n      100: '#ededed',\n      200: '#d3d3d3',\n      300: '#b3b3b3',\n      400: '#a0a0a0',\n      500: '#898989',\n      600: '#6c6c6c',\n      700: '#202020',\n      800: '#121212',\n      900: '#111'\n    }\n  }\n });\n\nexport default theme;"
  },
  {
    "path": "src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    \"allowJs\": false,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": false,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\"\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  }
]