diff --git a/.gitignore b/.gitignore
index a547bf36..7ceb59f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
+.env
diff --git a/README.md b/README.md
index f768e33f..c019ce6c 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,76 @@
-# React + Vite
+## Unit Assignment: Flixster
-This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+Submitted by: Gabriella
-Currently, two official plugins are available:
+Estimated time spent: 25 hours spent in total
-- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
-- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+Deployed Application (optional): [Flixster Deployed Site](https://flixster-starter-jpsb.onrender.com)
+
+### Application Features
+
+#### CORE FEATURES
+
+
+- [X] **Display Movies**
+ - [X] Users can view a list of current movies from The Movie Database API.
+ - [X] For each movie displayed, users can see its title, poster image, and votes.
+ - [X] Users can load more current movies by clicking a button at the bottom of the list (page should not be reloaded).
+- [X] **Search Functionality**
+ - [X] Users can search for movies and view the results in a grid.
+ - [X] Users can clear results and view previous current movies displayed.
+- [X] **Accessibility Features**
+ - [X] Website implements accessibility features (semantic HTML, color contrast, font sizing, alt text for images).
+- [X] **Responsive Design**
+ - [X] Website implements responsive web design.
+- [X] **Movie Details**
+ - [X] Users can view more details about a movie in a popup, such as runtime in minutes, backdrop poster, release date, genres, and/or an overview.
+- [X] **Sorting Options**
+ - [X] Users can click on a filter by drop down to sort product by type (alphabetic, release date, rating).
+- [X] **Layout**
+ - [X] Website displays header, banner, search, movie grid, about, contact, and footer section.
+
+#### STRETCH FEATURES
+
+- [X] **Deployment**
+ - [X] Website is deployed via Render.
+- [X] **Embedded Movie Trailers**
+ - [X] Within the popup displaying a movie's details, users can play the movie trailer.
+- [X] **Watched Checkbox**
+ - [X] For each movie displayed, users can mark the movie as watched.
+- [X] **Favorite Button**
+ - [X] For each movie displayed, users can favorite the movie.
+- [X] **Sidebar**
+ - [X] Users can open a sidebar
+ - [X] The sidebar displays the user's favorited and watched movies
+
+### Walkthrough Video
+
+
+### Reflection
+
+* Did the topics discussed in your labs prepare you to complete the assignment? Be specific, which features in your weekly assignment did you feel unprepared to complete?
+
+Yes the labs did prepare me
+
+* If you had more time, what would you have done differently? Would you have added additional features? Changed the way your project responded to a particular event, etc.
+
+I would have added users be able to click on the movies in the favorite tab and the modal for the movie dsplays.
+
+* Reflect on your project demo, what went well? Were there things that maybe didn't go as planned? Did you notice something that your peer did that you would like to try next time?
+
+I think being able to do most of the stretch feautres was the best thing. One thing that did not go as planned was having to move code around because i didnt understand passing props properling and how states should be higher up in componenets that need it.
+
+### Open-source libraries used
+
+- Add any links to open-source libraries used in your project.
+
+### Shout out
+
+Shout out to Theo
diff --git a/index.html b/index.html
index 0abcfe5f..de73b4dd 100644
--- a/index.html
+++ b/index.html
@@ -3,6 +3,7 @@
+
Flixster
diff --git a/src/App.css b/src/App.css
index 0bf65669..376d2ad8 100644
--- a/src/App.css
+++ b/src/App.css
@@ -17,12 +17,13 @@
width: 100%;
}
- .search-bar {
+ .searchBar {
flex-direction: column;
gap: 10px;
}
- .search-bar form {
+ .searchBar form {
flex-direction: column;
}
}
+
diff --git a/src/App.jsx b/src/App.jsx
index 48215b3f..ad60a4c5 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,10 +1,68 @@
-import { useState } from 'react'
+import { useState, useEffect } from 'react'
import './App.css'
+import Header from "./components/Header";
+import.meta.env.VITE_API_KEY;
+import NowPlayingScreen from './components/NowPlayingScreen';
+import SearchScreen from './components/SearchScreen.jsx';
+import Footer from './components/Footer.jsx';
const App = () => {
-
-
-
+ const [isSearching, setSearching] = useState(false);
+ const [criteria, setCriteria] = useState("");
+
+ const [isFave, setFave] = useState([]);
+ const [isWatched, setWatched] = useState([]);
+
+ function loadFromLocalStorage() {
+ let savedLists = localStorage.getItem("userLists");
+ if (savedLists == undefined || savedLists == null ||savedLists == "") {
+ return;
+ }
+ let ListsObject = JSON.parse(savedLists);
+ setFave(ListsObject.favList);
+ setWatched(ListsObject.watchedList);
+ }
+
+ /**
+ * If the watchlist are now empty, check for stored (usually after a page load)
+ * else: put the current state into localstorage
+ */
+ useEffect(() => {
+ if (isWatched.length == 0 && isFave.length == 0) {
+ loadFromLocalStorage()
+ } else {
+ localStorage.setItem("userLists", JSON.stringify(
+ {
+ favList: isFave,
+ watchedList: isWatched
+ }
+ ))
+ }
+ }, [isFave, isWatched])
+
+ const handleNewCriteria = (criteriaFromHeader) => {
+ setCriteria(criteriaFromHeader)
+ }
+
+ const handleSetFav = (newVal) => {
+ setFave(newVal);
+ }
+
+ const handleSetWatched = (newVal) => {
+ setWatched(newVal);
+ }
+
+ return (
+
+
+ {!isSearching ?
+
+ :
+
+ }
+
+
+ );
}
-export default App
+export default App;
diff --git a/src/components/FavMovies.jsx b/src/components/FavMovies.jsx
new file mode 100644
index 00000000..2276bd91
--- /dev/null
+++ b/src/components/FavMovies.jsx
@@ -0,0 +1,10 @@
+function FavMovies(props){
+ return(
+
+
+
{props.title}
+
+ )
+}
+
+export default FavMovies
\ No newline at end of file
diff --git a/src/components/Footer.css b/src/components/Footer.css
new file mode 100644
index 00000000..a95a4929
--- /dev/null
+++ b/src/components/Footer.css
@@ -0,0 +1,59 @@
+.footer {
+ background-color: #141414;
+ color: white;
+ padding: 20px 0;
+ text-align: center;
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+
+.footer-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+}
+
+.footer-links {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ margin-bottom: 20px;
+}
+
+.footer-link {
+ color: #e50914;
+ margin: 0 10px;
+ text-decoration: none;
+ transition: color 0.3s ease;
+}
+
+.footer-link:hover {
+ color: #ff1e1e;
+}
+
+.footer-social {
+ margin-bottom: 20px;
+}
+
+.social-link {
+ color: white;
+ margin: 0 10px;
+ text-decoration: none;
+ font-size: 18px;
+ transition: color 0.3s ease;
+}
+
+.social-link:hover {
+ color: #e50914;
+}
+
+.footer-bottom {
+ margin-top: 20px;
+}
+
+.footer-bottom p {
+ margin: 0;
+ font-size: 14px;
+ color: #888;
+}
\ No newline at end of file
diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx
new file mode 100644
index 00000000..9e03c515
--- /dev/null
+++ b/src/components/Footer.jsx
@@ -0,0 +1,29 @@
+import './Footer.css';
+
+const Footer = () => {
+ return (
+
+
+
+
© 2024 Gabriella, Inc.
+
+
+ );
+};
+
+export default Footer;
\ No newline at end of file
diff --git a/src/components/Header.css b/src/components/Header.css
new file mode 100644
index 00000000..f85f9aa9
--- /dev/null
+++ b/src/components/Header.css
@@ -0,0 +1,44 @@
+.header {
+ background-color:red;
+ padding: 10px 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ color: white;
+}
+
+.header h1 {
+ margin: 0;
+}
+
+.toggleButton {
+ display: flex;
+ align-items: center;
+}
+
+.header input {
+ padding: 5px;
+ font-size: 16px;
+ border: none;
+ border-radius: 5px;
+ margin-right: 10px;
+}
+
+.header button {
+ background-color: white;
+ color: #ff0000;
+ border: none;
+ padding: 5px 10px;
+ border-radius: 5px;
+ cursor: pointer;
+ margin-right: 10px;
+}
+
+.header .sortBy {
+ background-color: #ff0000;
+ color: white;
+ border: none;
+ padding: 5px 10px;
+ border-radius: 5px;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/src/components/Header.jsx b/src/components/Header.jsx
new file mode 100644
index 00000000..30dc11bb
--- /dev/null
+++ b/src/components/Header.jsx
@@ -0,0 +1,21 @@
+import './Header.css';
+
+function Header({setSearching,sortMovies }) {
+
+ return (
+
+
Flixster
+
+ setSearching(false)}>Now Playing
+ setSearching(true)}>Search
+ sortMovies(e.target.value)}>
+ Sort by Title
+ Sort by Date
+ Sort by Rating
+
+
+
+ );
+}
+
+export default Header;
\ No newline at end of file
diff --git a/src/components/ModalContainer.css b/src/components/ModalContainer.css
new file mode 100644
index 00000000..d39e5820
--- /dev/null
+++ b/src/components/ModalContainer.css
@@ -0,0 +1,86 @@
+.modalOverlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ backdrop-filter: blur(5px);
+ overflow: auto;
+ background: rgba(0, 0, 0, 0.75);
+}
+
+.modalContent {
+ background: #141414;
+ margin: auto;
+ margin-top: 10%;
+ padding: 20px;
+ border-radius: 10px;
+ width: 50%;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+ position: relative;
+ color: white;
+ overflow: auto;
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+
+.modalContent div {
+ margin-bottom: 10px;
+}
+
+.closeButton {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ background: none;
+ border: none;
+ font-size: 24px;
+ cursor: pointer;
+ color: white;
+}
+
+.modalBody {
+ margin-top: 20px;
+}
+
+.modalBody div {
+ margin-bottom: 10px;
+ font-size: 16px;
+}
+
+.modalBody img {
+ border-radius: 5px;
+}
+
+.modalBody div:first-child {
+ font-size: 24px;
+ font-weight: bold;
+ color: #e50914;
+}
+
+.modalBody div:nth-child(3) {
+ font-size: 18px;
+ color: #e50914;
+}
+
+.modalContent div:nth-child(4){
+ color:white;
+}
+
+.modalContent div:nth-child(5){
+ color:white;
+}
+
+button {
+ background-color: #e50914;
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 5px;
+ cursor: pointer;
+ transition: background-color 0.3s;
+ font-weight: bold;
+}
+
+button:hover {
+ background-color: #ff1e1e;
+}
\ No newline at end of file
diff --git a/src/components/ModalContainer.jsx b/src/components/ModalContainer.jsx
new file mode 100644
index 00000000..35948180
--- /dev/null
+++ b/src/components/ModalContainer.jsx
@@ -0,0 +1,53 @@
+import "./ModalContainer.css";
+import {useEffect, useState} from "react";
+
+function ModalContainer(props){
+ const[trailerUrl, setTrailerUrl]= useState("")
+ const apiKey = import.meta.env.VITE_API_KEY;
+
+ useEffect(()=> {
+ const options = {
+ method: 'GET',
+ headers: {
+ accept: 'application/json',
+ Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA'
+ }
+ };
+
+ fetch(`https://api.themoviedb.org/3/movie/${props.data.id}/videos?api_key=${apiKey}`, options)
+ .then(response => response.json())
+ .then(response => response.results.find(
+ (video) => video.site === "YouTube" && video.type === "Trailer"
+ ))
+ .then((trailer) => setTrailerUrl(`https://www.youtube.com/embed/${trailer.key}`))
+ .catch(err => console.error(err));
+ }, [props.data.id, apiKey])
+
+
+ return(
+
+
+
+ ×
+
+
+
Title:{props.data.title}
+
+
Original Title: {props.data.title}
+
Overview: {props.data.overview}
+
Release Date: {props.data.releaseDate}
+
+
+
+
+
+ );
+}
+
+export default ModalContainer;
\ No newline at end of file
diff --git a/src/components/MovieCards.jsx b/src/components/MovieCards.jsx
new file mode 100644
index 00000000..38f276de
--- /dev/null
+++ b/src/components/MovieCards.jsx
@@ -0,0 +1,49 @@
+import "./movieCards.css"
+import { useState } from "react";
+import ModalContainer from "./ModalContainer";
+
+
+function MovieCard (props) {
+ const[isModalOpen, setModal]=useState(false)
+ const[isFav, setIsFave] = useState(false);
+ const[watched, setWatched]= useState(false)
+
+ function openModal(){
+ setModal(true)
+ }
+ function closeModal()
+ {
+ setModal(false)
+ }
+
+ return(
+ <>
+
+
+
{props.title}
+
Rating: {props.rating}
+
+
+ {
+ e.stopPropagation();
+ setIsFave(!isFav);
+ props.favoriteMovies();
+ }}>{isFav ? '❤️':'🖤'}
+
+
+ {
+ e.stopPropagation();
+ setWatched(!watched);
+ props.watchedMovies();
+ }}>
+ Watched: {watched ? '☑️' : '🔘'}
+
+
+
+
+ {isModalOpen ? : null}
+ >
+ );
+}
+
+export default MovieCard;
\ No newline at end of file
diff --git a/src/components/MovieList.jsx b/src/components/MovieList.jsx
new file mode 100644
index 00000000..d3075bf9
--- /dev/null
+++ b/src/components/MovieList.jsx
@@ -0,0 +1,29 @@
+import MovieCards from "./MovieCards"
+import "./movieCards.css"
+
+function MovieList(props){
+ function createCards(card, index){
+ return(
+ props.favMovies(card.id)}
+ watchedMovies={() =>props.watchedMovies(card.id)}
+ />
+ )
+ }
+
+ return(
+
+ {props.data.map(createCards)}
+
+ )
+}
+
+export default MovieList;
diff --git a/src/components/NowPlayingScreen.css b/src/components/NowPlayingScreen.css
new file mode 100644
index 00000000..e69de29b
diff --git a/src/components/NowPlayingScreen.jsx b/src/components/NowPlayingScreen.jsx
new file mode 100644
index 00000000..b9495c0a
--- /dev/null
+++ b/src/components/NowPlayingScreen.jsx
@@ -0,0 +1,84 @@
+/* eslint-disable react/prop-types */
+import {useEffect, useState} from "react";
+import MovieList from "./MovieList";
+import SideBar from "./SideBar";
+const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN;
+
+
+function NowPlayingScreen({criteriaFromHeader, setFave, isFave, setWatched, isWatched}){
+ const [movies, setMovies]=useState([]);
+ const [pageNumber, setPageNumber]=useState(1);
+ const[url, setUrl]=useState(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${pageNumber}`)
+
+ function addToFave(movieId){
+ if(isFave.includes(movieId)){
+ setFave(prevIds => prevIds.filter(prevId => prevId !== movieId))
+
+ } else{
+ setFave(prevId => [...prevId, movieId])
+ }
+ }
+
+ function addToWatched(movieId){
+ if(isWatched.includes(movieId)){
+ setWatched(prevIds => prevIds.filter(prevId => prevId !== movieId))
+
+ } else {
+ setWatched(prevId => [...prevId, movieId])
+ }
+ }
+
+ useEffect(()=>{
+ const options = {
+ method: 'GET',
+ headers: {
+ accept: 'application/json',
+ Authorization: `Bearer ${ACCESS_TOKEN}`
+ }
+ };
+
+ fetch(url, options)
+ .then(response => response.json())
+ .then(response => {
+ handleSortMovies(criteriaFromHeader, movies.concat(response.results))
+
+ })
+ .catch(err => console.error(err));
+ }, [url, pageNumber]);
+
+ function loadMore(){
+ setPageNumber(prevPageNumber => prevPageNumber+1)
+ setUrl(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${pageNumber + 1}`);
+ }
+
+ function handleSortMovies(criteria, parmaMovies = movies){
+
+ const sortedMovies = [...parmaMovies].sort((a, b) => {
+
+ if (criteria === 'title') {
+ return a.title.localeCompare(b.title);
+ } else if (criteria === 'date') {
+ return new Date(a.release_date) - new Date(b.release_date);
+ } else if (criteria === 'rating') {
+ return b.vote_average - a.vote_average;
+ }
+ return 0;
+ });
+ setMovies(sortedMovies);
+ }
+
+ useEffect(function() {
+ handleSortMovies(criteriaFromHeader)
+ }, [criteriaFromHeader])
+
+ return(
+
+
+
+ Load more
+
+
+ )
+}
+
+export default NowPlayingScreen;
\ No newline at end of file
diff --git a/src/components/SearchScreen.jsx b/src/components/SearchScreen.jsx
new file mode 100644
index 00000000..8d016065
--- /dev/null
+++ b/src/components/SearchScreen.jsx
@@ -0,0 +1,70 @@
+/* eslint-disable react/prop-types */
+import { useState} from "react";
+import MovieList from "./MovieList";
+import SideBar from "./SideBar";
+
+function SearchScreen({setFave, isFave, setWatched, isWatched}){
+ const[query, setSearchQuery]= useState('')
+ const[movies, setMovies]= useState([]);
+ const[pageNumber, setPageNumber]= useState(1)
+
+
+ function addToFave(movieId){
+ if(isFave.includes(movieId)){
+ setFave(prevIds => prevIds.filter(prevId => prevId !== movieId))
+
+ } else{
+ setFave(prevId => [...prevId, movieId])
+ }
+ }
+
+ function addToWatched(movieId){
+ if(isWatched.includes(movieId)){
+ setWatched(prevIds => prevIds.filter(prevId => prevId !== movieId))
+
+ } else {
+ setWatched(prevId => [...prevId, movieId])
+ }
+ }
+
+
+
+ const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN;
+
+ function handleSearch(searchQuery){
+ const options = {
+ method: 'GET',
+ headers: {
+ accept: 'application/json',
+ Authorization: `Bearer ${ACCESS_TOKEN}`
+ }
+ };
+
+ fetch(`https://api.themoviedb.org/3/search/movie?query=${searchQuery}&include_adult=false&language=en-US&page=${pageNumber}`, options)
+ .then(response => response.json())
+ .then(response => setMovies(response.results))
+ .catch(err => console.error(err));
+ }
+
+ function loadMore(){
+ setPageNumber(prevPageNumber => prevPageNumber+1)
+ }
+
+ return(
+
+
+ setSearchQuery(e.target.value.toLowerCase())}
+ />
+ handleSearch(query)}>Search
+
+
+ Load more
+
+ )
+}
+
+export default SearchScreen;
\ No newline at end of file
diff --git a/src/components/SideBar.jsx b/src/components/SideBar.jsx
new file mode 100644
index 00000000..97037543
--- /dev/null
+++ b/src/components/SideBar.jsx
@@ -0,0 +1,65 @@
+/* eslint-disable react/prop-types */
+import "./sideBar.css";
+import FavMovies from "./FavMovies";
+import { useEffect, useState } from "react";
+
+function SideBar(props) {
+ const [favMovieElements, setFavMovieElements] = useState([]);
+ const [watchedMovieElements, setWatchedMovieElements] = useState([]);
+
+ async function getMovie(movieId) {
+ const options = {
+ method: 'GET',
+ headers: {
+ accept: 'application/json',
+ Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA'
+ }
+ };
+
+ let response = await fetch(`https://api.themoviedb.org/3/movie/${movieId}`, options)
+ response = response.json();
+ return response;
+ }
+
+ async function MovieCard(movieId) {
+
+
+ const movieIndex = props.movies.findIndex((item) => item.id === movieId)
+ let movie;
+ if (movieIndex == -1) {
+ movie = await getMovie(movieId);
+ } else {
+ movie = props.movies[movieIndex]
+ }
+
+ return movie;
+ }
+
+
+ useEffect(() => {
+ Promise.all(props.fave.map((id) => (MovieCard(id)))).then((values) => {
+ setFavMovieElements(values)
+ });
+ Promise.all(props.watched.map((id) => (MovieCard(id)))).then((values) => {
+ setWatchedMovieElements(values)
+ });
+
+
+ }, [props.watched, props.fave])
+
+ return (
+
+
Favorites
+ {favMovieElements.map((movie, index) => {
+ return ( )
+ })}
+ Watched
+ {watchedMovieElements.map((movie, index) => {
+ return ( )
+ })}
+
+
+ );
+};
+
+export default SideBar;
\ No newline at end of file
diff --git a/src/components/movieCards.css b/src/components/movieCards.css
new file mode 100644
index 00000000..26cb8650
--- /dev/null
+++ b/src/components/movieCards.css
@@ -0,0 +1,51 @@
+.movieCard {
+ background-color: #ff0000;
+ color: white;
+ width: 200px;
+ margin: 10px;
+ padding: 10px;
+ border-radius: 10px;
+ text-align: center;
+}
+.movieCard:hover {
+ transform: scale(1.0);
+ box-shadow: 0 7px 14px black;
+
+}
+.movieCard img {
+ width: 100%;
+ border-radius: 10px;
+}
+
+.favoriteWatchedIcon{
+ display:flex;
+}
+.favoriteWatchedIcon button{
+ background-color:red;
+ cursor: pointer;
+ font-size: 16px;
+ font-weight: bold;
+ transition: background-color 0.3s ease;
+}
+.movieCard h2 {
+ font-size: 18px;
+ margin: 10px 0 5px;
+}
+
+.movieCard p {
+ margin: 5px 0;
+}
+
+.movieList {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ padding: 20px;
+ margin-left: 13vw;
+}
+
+.favoriteIcon {
+ cursor: pointer;
+ font-size: 15px;
+ color: #ffd700; /* Gold color for favorite star */
+ }
\ No newline at end of file
diff --git a/src/components/sideBar.css b/src/components/sideBar.css
new file mode 100644
index 00000000..f7f370fd
--- /dev/null
+++ b/src/components/sideBar.css
@@ -0,0 +1,15 @@
+.sidebar {
+ height: 100vh;
+ width: 250px;
+ position: fixed;
+ top: 0;
+ left: 0;
+ background-color: red;
+ padding-top: 20px;
+ overflow: auto;
+ }
+
+ .sidebar h2 {
+ color: white;
+ text-align: center;
+ }
\ No newline at end of file