Repository: yasi-ayazi/movieapplication
Pull request #2: Js2/week2
Author: login: yasi-ayazi
Created: 2025-03-11T13:29:00Z | Merged: 2025-03-16T20:17:14Z

Changed the app to show multiple movies instead of one
Improved movie structure (added genre, rating)
Display movies in a grid layout
Added search by title and actor
Added sorting (A-Z, Z-A, Year) and genre filter
Show comments only for the selected movie
Improved UI with badges for actors and director, and better description display


Diff:
diff --git a/README.md b/README.md
index 0736b34..973b2ef 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,113 @@
-# movieapplication
+# 🎮 Movie App
+
+![GitHub repo size](https://img.shields.io/github/repo-size/yasi-ayazi/movieapplication)
+![GitHub contributors](https://img.shields.io/github/contributors/yasi-ayazi/movieapplication)
+![GitHub stars](https://img.shields.io/github/stars/yasi-ayazi/movieapplication?style=social)
+![GitHub license](https://img.shields.io/github/license/yasi-ayazi/movieapplication)
+
+## 📌 Overview
+This is a **Movie Listing & Search App** built as a learning project for **HackYourFuture**. The application allows users to:
+- 📌 Browse a list of movies with details (title, director, actors, genre, price, rating, and description).
+- 🔎 Search movies by **title, actor, or genre**.
+- 🔄 Sort movies by **title (A-Z, Z-A) or year (oldest, newest)**.
+- 🎭 Filter movies by **genre** using a dropdown.
+- 💬 Toggle and add **comments** to each movie.
+
+## 🚀 Technologies Used
+- ![HTML5](https://img.shields.io/badge/HTML5-E34F26?style=flat&logo=html5&logoColor=white)
+- ![CSS3](https://img.shields.io/badge/CSS3-1572B6?style=flat&logo=css3&logoColor=white)
+- ![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?style=flat&logo=javascript&logoColor=black)
+
+## 📂 Project Structure
+```
+📺 movie-app
+ ├ 📂 scripts
+ ┃ ├ 📋 app.js           # Main application logic (event listeners & app initialization)
+ ┃ ├ 📋 data.js          # Movie data (structured objects)
+ ┃ ├ 📋 render.js        # UI rendering (creates movie cards dynamically)
+ ┃ ├ 📋 filters.js       # Search & sorting logic
+ ┃ ├ 📋 comments.js      # Handles comment toggling & submission
+ ├ 📂 styles
+ ┃ ├ 📋 app.css          # Styling for the entire application
+ ├ 📋 index.html         # HTML structure
+ ├ 📋 README.md          # Project documentation
+```
+
+---
+
+## 🛠️ Installation & Setup
+
+### ✅ **1. Clone the repository**
+```sh
+git clone https://github.com/yasi-ayazi/movieapplication.git
+cd movieapplication
+```
+
+### ✅ **2. Open `index.html` in a browser**
+You can directly open the `index.html` file in your browser or use a live server:
+```sh
+npx live-server
+```
+_(Requires Node.js installed)_
+
+---
+
+## 🔎 Features
+### 🎮 **Movies List**
+- Dynamically rendered using **JavaScript**.
+- Includes **title, genre, rating, director, actors, price, and description**.
+
+### 🔍 **Search & Filtering**
+- **Search**: Users can search by **title or actor**.
+- **Genre Filter**: Dropdown allows users to filter movies by genre.
+- **Sorting**: Users can sort movies by **title** or **year**.
+
+### 💬 **Comments System**
+- Each movie has a **toggle button** for comments.
+- Users can **add comments**, which persist on the page.
+
+---
+
+## 📌 How It Works
+### 🗼 **Rendering Movies**
+- The movies are stored in **`data.js`** as an array of objects.
+- The app dynamically creates movie cards using **`render.js`**.
+
+### 🔄 **Search, Filter, and Sort**
+- **User inputs** are processed in **`filters.js`**.
+- Filters update the movie list dynamically.
+
+### 🎭 **Commenting System**
+- **`comments.js`** handles comment toggling and submission.
+- Only the **clicked movie’s comments** are shown (others remain hidden).
+
+---
+
+## 🏰 Future Enhancements
+This project is designed to be **extendable** for learning purposes. Some planned features include:
+- ✅ **Persisting Comments** (Save them in `localStorage` or a backend).
+- ✅ **Movie Details Page** (Click on a movie to see a detailed page).
+- ✅ **Add Movies Feature** (Form to allow users to add new movies).
+- ✅ **User Ratings** (Users can rate movies).
+- ✅ **Dark Mode Support**.
+
+---
+
+## 👨‍💻 Contribution Guide
+Contributions are welcome! If you want to improve the project:
+1. **Fork the repository**.
+2. **Create a feature branch** (`git checkout -b feature-xyz`).
+3. **Commit changes** (`git commit -m "Added xyz feature"`).
+4. **Push and create a pull request**.
+
+---
+
+## ⚡ Author & Acknowledgements
+This project is part of **HackYourFuture** and is developed as a learning experience. Special thanks to the **HYF mentors** for guidance. 🙌  
+
+---
+
+## 🔗 License
+![MIT License](https://img.shields.io/badge/License-MIT-green.svg)  
+This project is **open-source** under the MIT License. Feel free to use and extend it!
+
diff --git a/index.html b/index.html
index 6d3e54d..6fb99de 100644
--- a/index.html
+++ b/index.html
@@ -4,10 +4,13 @@
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>Movie Reviews</title>
-    <link rel="stylesheet" href="./styles/movie-card.css" />
+    <link rel="stylesheet" href="./styles/app.css" />
   </head>
   <body>
-    <div class="movies-container" id="movies-container"></div>
-    <script src="./scripts/movie-card.js"></script>
+    <main id="main" class="main-content">
+      <div class="movies-container" id="movies-container"></div>
+    </main>
+
+    <script type="module" src="./scripts/app.js"></script>
   </body>
 </html>
diff --git a/scripts/app.js b/scripts/app.js
new file mode 100644
index 0000000..ce18b5d
--- /dev/null
+++ b/scripts/app.js
@@ -0,0 +1,20 @@
+import { renderMovies, createSearchAndSortBar } from "./render.js";
+import { handleCommentToggle, handleCommentSubmission } from "./comments.js";
+import { filterAndSortMovies } from "./filters.js";
+import { movies } from "./data.js";
+
+createSearchAndSortBar();
+
+renderMovies(movies);
+
+document
+  .getElementById("sort-select")
+  .addEventListener("change", filterAndSortMovies);
+document
+  .getElementById("search-input")
+  .addEventListener("input", filterAndSortMovies);
+document
+  .getElementById("genre-select")
+  .addEventListener("change", filterAndSortMovies);
+document.addEventListener("click", handleCommentToggle);
+document.addEventListener("click", handleCommentSubmission);
diff --git a/scripts/comments.js b/scripts/comments.js
new file mode 100644
index 0000000..ac5e5d7
--- /dev/null
+++ b/scripts/comments.js
@@ -0,0 +1,60 @@
+function createCommentSection(movieId) {
+  return `
+    <div class="comment-section" id="comment-section-${movieId}" style="display: none;">
+      <form class="comment-form">
+        <textarea id="comment-${movieId}" class="comment-textarea" placeholder="Write your comment here..."></textarea>
+        <button type="submit" class="comment-btn" data-movie-id="${movieId}">Submit</button>
+      </form>
+      <ul id="comment-list-${movieId}" class="comment-list"></ul>
+    </div>
+  `;
+}
+
+// Handles toggling the visibility of the comment section.
+function handleCommentToggle(event) {
+  if (event.target.classList.contains("toggle-comment")) {
+    const movieId = event.target.getAttribute("data-movie-id");
+    const commentSection = document.getElementById(
+      `comment-section-${movieId}`
+    );
+
+    // Hide all other comment sections
+    document.querySelectorAll(".comment-section").forEach((section) => {
+      if (section.id !== `comment-section-${movieId}`) {
+        section.style.display = "none";
+        section.previousElementSibling.innerHTML = "Show Comments"; 
+      }
+    });
+
+    // Toggle the clicked movie’s visibility
+    commentSection.style.display =
+      commentSection.style.display === "none" ||
+      commentSection.style.display === ""
+        ? "block"
+        : "none";
+
+    event.target.textContent =
+      commentSection.style.display === "block"
+        ? "Hide Comments"
+        : "Show Comments";
+  }
+}
+
+// Adding a comment with clicking the "Submit" button.
+function handleCommentSubmission(event) {
+  if (event.target.classList.contains("comment-btn")) {
+    event.preventDefault();
+    const movieId = event.target.getAttribute("data-movie-id");
+    const commentInput = document.getElementById(`comment-${movieId}`);
+    const commentList = document.getElementById(`comment-list-${movieId}`);
+
+    if (commentInput.value.trim() !== "") {
+      const newComment = document.createElement("li");
+      newComment.textContent = commentInput.value;
+      commentList.appendChild(newComment);
+      commentInput.value = "";
+    }
+  }
+}
+
+export { createCommentSection, handleCommentToggle, handleCommentSubmission };
diff --git a/scripts/data.js b/scripts/data.js
new file mode 100644
index 0000000..b792e2a
--- /dev/null
+++ b/scripts/data.js
@@ -0,0 +1,82 @@
+export const movies = [
+  {
+    id: 1,
+    title: 'Interstellar',
+    description:
+      'The adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.',
+    movie_year: 2014,
+    director: "Christopher Nolan",
+    actors: ["Matthew McConaughey", "Anne Hathaway", "Jessica Chastain", "Michael Caine", "Casey Affleck", "Mackenzie Foy", "John Lithgow", "Ellen Burstyn", "Matt Damon"],
+    poster_url: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/gEU2QniE6E77NI6lCU6MxlNBvIx.jpg',
+    price: '120.00',
+    genre: "Sci-Fi",
+    rating: 8.7
+  },
+  {
+    id: 2,
+    title: 'The Godfather',
+    description:
+      'The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.',
+    movie_year: 1972,
+    director: "Francis Ford Coppola",
+    actors: ["Marlon Brando", "Al Pacino", "James Caan", "Diane Keaton", "Robert Duvall", "Talia Shire"],
+    poster_url: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/3bhkrj58Vtu7enYsRolD1fZdja1.jpg',
+    price: '150.00',
+    genre: "Crime, Drama",
+    rating: 9.2
+  },
+  {
+    id: 3,
+    title: 'Parasite',
+    description:
+      'Greed and class discrimination threaten the newly formed symbiotic relationship between the wealthy Park family and the destitute Kim clan.',
+    movie_year: 2019,
+    director: "Bong Joon-ho",
+    actors: ["Song Kang-ho", "Lee Sun-kyun", "Cho Yeo-jeong", "Choi Woo-shik", "Park So-dam"],
+    poster_url: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/7IiTTgloJzvGI1TAYymCfbfl3vT.jpg',
+    price: '110.00',
+    genre: "Thriller, Drama",
+    rating: 8.6
+  },
+  {
+    id: 4,
+    title: 'Pulp Fiction',
+    description:
+      'The lives of two mob hitmen, a boxer, a gangster and his wife, and a pair of diner bandits intertwine in four tales of violence and redemption.',
+    movie_year: 1994,
+    director: "Quentin Tarantino",
+    actors: ["John Travolta", "Uma Thurman", "Samuel L. Jackson", "Bruce Willis", "Harvey Keitel", "Tim Roth"],
+    poster_url: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/d5iIlFn5s0ImszYzBPb8JPIfbXD.jpg',
+    price: '130.00',
+    genre: "Crime, Drama",
+    rating: 8.9
+  },
+  {
+    id: 5,
+    title: 'Inception',
+    description:
+      'A skilled thief, the absolute best in the dangerous art of extraction, stealing valuable secrets from deep within the subconscious during the dream state, is given a chance at redemption.',
+    movie_year: 2010,
+    director: "Christopher Nolan",
+    actors: ["Leonardo DiCaprio", "Joseph Gordon-Levitt", "Elliot Page", "Tom Hardy", "Ken Watanabe", "Cillian Murphy"],
+    poster_url: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/ljsZTbVsrQSqZgWeep2B1QiDKuh.jpg',
+    price: '135.00',
+    genre: "Action, Sci-Fi",
+    rating: 8.8
+  },
+  {
+    id: 6,
+    title: 'The Dark Knight',
+    description:
+      'When the menace known as the Joker emerges from his mysterious past, he wreaks havoc and chaos on the people of Gotham. The Dark Knight must accept one of the greatest psychological and physical tests of his ability to fight injustice.',
+    movie_year: 2008,
+    director: "Christopher Nolan",
+    actors: ["Christian Bale", "Heath Ledger", "Aaron Eckhart", "Michael Caine", "Maggie Gyllenhaal", "Gary Oldman", "Morgan Freeman"],
+    poster_url: 'https://www.themoviedb.org/t/p/w600_and_h900_bestv2/qJ2tW6WMUDux911r6m7haRef0WH.jpg',
+    price: '140.00',
+    genre: "Action, Crime, Drama",
+    rating: 9.0
+  }
+];
+
+
diff --git a/scripts/filters.js b/scripts/filters.js
new file mode 100644
index 0000000..084a620
--- /dev/null
+++ b/scripts/filters.js
@@ -0,0 +1,44 @@
+import { movies } from "./data.js";
+import { renderMovies } from "./render.js";
+
+// Filters and sorts movies based on user input.
+function filterAndSortMovies() {
+  const searchQuery = document
+    .getElementById("search-input")
+    .value.toLowerCase();
+  const sortOption = document.getElementById("sort-select").value;
+  const selectedGenre = document.getElementById("genre-select").value;
+// Filter movies base on title or acters or genre
+  const filteredMovies = movies.filter(
+    (movie) =>
+      (movie.title.toLowerCase().includes(searchQuery) ||
+        movie.actors.some((actor) =>
+          actor.toLowerCase().includes(searchQuery)
+        )) &&
+      (selectedGenre === "all" || movie.genre.includes(selectedGenre))
+  );
+
+  switch (sortOption) {
+    case "title-asc":
+      filteredMovies.sort((a, b) => a.title.localeCompare(b.title));
+      break;
+    case "title-desc":
+      filteredMovies.sort((a, b) => b.title.localeCompare(a.title));
+      break;
+    case "year-asc":
+      filteredMovies.sort((a, b) => a.movie_year - b.movie_year);
+      break;
+    case "year-desc":
+      filteredMovies.sort((a, b) => b.movie_year - a.movie_year);
+      break;
+  }
+  if (filteredMovies.length === 0) {  
+    const moviesContainer = document.getElementById("movies-container");  
+    moviesContainer.innerHTML = "<p>No movies found</p>";  
+    return;  
+  }  
+
+  renderMovies(filteredMovies);
+}
+
+export { filterAndSortMovies };
diff --git a/scripts/movie-card.js b/scripts/movie-card.js
deleted file mode 100644
index 9eeae7c..0000000
--- a/scripts/movie-card.js
+++ /dev/null
@@ -1,99 +0,0 @@
-const movie = {
-  id: 1,
-  title: "Interstellar",
-  description:
-    "The adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.",
-  movie_year: 2014,
-  director: "Christopher Nolan",
-  actors: [
-    "Matthew McConaughey",
-    "Anne Hathaway",
-    "Jessica Chastain",
-    "Michael Caine",
-    "Casey Affleck",
-    "Mackenzie Foy",
-    "John Lithgow",
-    "Ellen Burstyn",
-    "Matt Damon",
-  ],
-  poster_url:
-    "https://www.themoviedb.org/t/p/w600_and_h900_bestv2/gEU2QniE6E77NI6lCU6MxlNBvIx.jpg",
-  price: "120.00",
-};
-function renderMovieCard() {
-  const moviesContainer = document.getElementById("movies-container");
-
-  function createMovieCard(movie) {
-    const movieCard = document.createElement("div");
-    movieCard.classList.add("movie-card");
-
-    movieCard.innerHTML = `
-            <img src="${movie.poster_url}" alt="${movie.title}" class="movie-poster">
-            <div class="movie-info">
-                <div class="movie-title">${movie.title}</div>
-                <div class="movie-date">Released: ${movie.movie_year}</div>
-                <div class="movie-director">Director: ${movie.director}</div>
-                <div class="movie-price">Price: <span class= "movie-price-number">$${movie.price}</span></div>
-                <div class="movie-description">${movie.description}</div>
-            </div>
-        `;
-
-    return movieCard;
-  }
-  function initializeComments(movieId) {
-    const commentSection = document.createElement("div");
-    commentSection.classList.add("comment-section");
-
-    commentSection.innerHTML = `
-            <div class="comment-title">Leave a Comment</div>
-            <form class="comment-form">
-                <textarea id="comment-${movieId}" placeholder="Write your comment here..." class="comment-textarea"></textarea>
-                <div class="comment-btn-wrapper">
-                    <button class="comment-btn" data-movie-id="${movieId}">Submit</button>
-                </div>
-            </form>
-            <ul id="comment-list-${movieId}" class="comment-list"></ul>
-        `;
-    document.addEventListener("click", function (event) {
-      event.preventDefault();
-      if (event.target.classList.contains("comment-btn")) {
-        const movieId = event.target.getAttribute("data-movie-id");
-        const commentInput = document.getElementById(`comment-${movieId}`);
-        const commentList = document.getElementById(`comment-list-${movieId}`);
-
-        if (commentInput && commentList && commentInput.value.trim() !== "") {
-          const newComment = document.createElement("li");
-          newComment.textContent = commentInput.value;
-          commentList.appendChild(newComment);
-          commentInput.value = "";
-        }
-      }
-    });
-    return commentSection;
-  }
-  function createActorsSection(actors) {
-    const actorsSection = document.createElement("div");
-    actorsSection.classList.add("actors-section");
-
-    actors.forEach((actor) => {
-      const actorBadge = document.createElement("span");
-      actorBadge.classList.add("actor-badge");
-      actorBadge.textContent = actor;
-      actorsSection.appendChild(actorBadge);
-    });
-
-    return actorsSection;
-  }
-
-  if (moviesContainer) {
-    const movieCardElement = createMovieCard(movie);
-    const actorsSectionElement = createActorsSection(movie.actors);
-    const commentSectionElement = initializeComments(movie.id);
-
-    moviesContainer.appendChild(movieCardElement);
-    moviesContainer.appendChild(actorsSectionElement);
-    moviesContainer.appendChild(commentSectionElement);
-  }
-}
-
-renderMovieCard();
diff --git a/scripts/render.js b/scripts/render.js
new file mode 100644
index 0000000..1e3b810
--- /dev/null
+++ b/scripts/render.js
@@ -0,0 +1,86 @@
+import { movies } from "./data.js";
+import { createCommentSection } from "./comments.js";
+
+function createSearchAndSortBar() {
+  const mainContent = document.getElementById("main");
+  const searchSortContainer = document.createElement("div");
+  searchSortContainer.classList.add("search-sort");
+
+  const genres = [
+    ...new Set(movies.flatMap((movie) => movie.genre.split(", "))),
+  ];
+
+  searchSortContainer.innerHTML = `
+    <div class="filter__wrapper">
+      <label for="search-input">Search Movies / Actors:</label>
+      <input class="filter__input" type="text" id="search-input" placeholder="Search by title or actor..." />
+    </div>
+    <div class="filter__wrapper">
+      <label for="sort-select">Sort Movies:</label>
+      <select class="filter__input" id="sort-select">
+        <option value="title-asc">Title (A-Z)</option>
+        <option value="title-desc">Title (Z-A)</option>
+        <option value="year-asc">Year (Oldest First)</option>
+        <option value="year-desc">Year (Newest First)</option>
+      </select>
+    </div>
+    <div class="filter__wrapper">
+      <label for="genre-select">Filter by Genre:</label>
+      <select class="filter__input" id="genre-select">
+        <option value="all">All Genres</option>
+        ${genres
+          .map((genre) => `<option value="${genre}">${genre}</option>`)
+          .join("")}
+      </select>
+    </div>
+  `;
+
+  mainContent.insertBefore(
+    searchSortContainer,
+    document.getElementById("movies-container")
+  );
+}
+
+function renderMovies(movieList) {
+  const moviesContainer = document.getElementById("movies-container");
+  moviesContainer.innerHTML = "";
+
+  movieList.forEach((movie) => {
+    const movieCard = createMovieCard(movie);
+    moviesContainer.appendChild(movieCard);
+  });
+}
+
+function createMovieCard(movie) {
+  const movieCard = document.createElement("div");
+  movieCard.classList.add("movie-card");
+
+  movieCard.innerHTML = `
+    <img src="${movie.poster_url}" alt="${movie.title}" class="movie-poster">
+    <div class="movie-info">
+      <h2 class="movie-title">${movie.title}</h2>
+      <p class="movie-date"><strong>Released:</strong> ${movie.movie_year}</p>
+      <p class="movie-genre"><strong>Genre:</strong> ${movie.genre
+        .split(", ")
+        .map((genre) => `<span class="pill">${genre}</span>`)
+        .join(" ")}</p>
+      <p class="movie-price"><strong>Price:</strong> $${movie.price}</p>
+      <p class="movie-rating"><strong>Rating:</strong> ⭐ ${movie.rating}</p>
+      <p class="movie-director"><strong>Director:</strong> <span class="badge">${
+        movie.director
+      }</span></p>
+      <p class="movie-actors"><strong>Actors:</strong> ${movie.actors
+        .map((actor) => `<span class="badge">${actor}</span>`)
+        .join(" ")}</p>
+      <p class="movie-description">${movie.description}</p>
+      <button class="toggle-comment" data-movie-id="${
+        movie.id
+      }">Show Comments</button>
+      ${createCommentSection(movie.id)}
+    </div>
+  `;
+
+  return movieCard;
+}
+
+export { renderMovies, createSearchAndSortBar };
\ No newline at end of file
diff --git a/styles/app.css b/styles/app.css
new file mode 100644
index 0000000..80e8505
--- /dev/null
+++ b/styles/app.css
@@ -0,0 +1,159 @@
+body {
+  font-family: "Raleway", sans-serif;
+  margin: 0;
+}
+
+.main-content {
+  max-width: 1000px;
+  margin: auto;
+  padding: 20px;
+}
+
+.search-sort {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  flex-wrap: wrap;
+}
+
+.filter__wrapper {
+  display: flex;
+  flex-direction: column;
+  margin-right: 10px;
+}
+
+label {
+  font-size: 14px;
+  font-weight: bold;
+  margin-bottom: 5px;
+}
+
+.filter__input {
+  padding: 8px;
+  border: 1px solid #ccc;
+  border-radius: 5px;
+  width: 250px;
+}
+
+.movies-container {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); /* 3 Columns */
+  gap: 15px;
+}
+
+.movie-card {
+  background: #f0f0f0;
+  padding: 15px;
+  border-radius: 10px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+  text-align: center;
+}
+
+.movie-poster {
+  width: 100%;
+  max-width: 200px;
+  height: auto;
+  border-radius: 8px;
+  margin-bottom: 10px;
+}
+
+.movie-info {
+  font-size: 14px;
+  text-align: left;
+}
+
+.movie-description {
+  font-size: 12px;
+  color: #555;
+  max-height: 100px;
+  overflow-y: auto;
+  padding: 5px;
+  background: #f9f9f9;
+  border-radius: 5px;
+}
+
+.pill {
+  background: #0d47a1;
+  color: white;
+  font-size: 12px;
+  padding: 4px 8px;
+  border-radius: 15px;
+  display: inline-block;
+  margin: 2px;
+}
+
+.toggle-comment {
+  width: 100%;
+  margin-top: 10px;
+  background: #032541;
+  color: white;
+  border: none;
+  padding: 8px;
+  border-radius: 5px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.toggle-comment:hover {
+  background: #001f3f;
+}
+
+.comment-section {
+  display: none;
+  margin-top: 10px;
+  padding: 10px;
+  background: #e8e8e8;
+  border-radius: 8px;
+}
+
+.comment-section textarea {
+  width: 100%;
+  height: 60px;
+  padding: 5px;
+  border: 1px solid #ccc;
+  border-radius: 5px;
+  font-size: 12px;
+  resize: none;
+}
+
+.comment-btn {
+  width: 100%;
+  margin-top: 5px;
+  padding: 6px;
+  background: #007bff;
+  color: white;
+  border: none;
+  border-radius: 5px;
+  cursor: pointer;
+  font-size: 12px;
+}
+
+.comment-btn:hover {
+  background: #0056b3;
+}
+
+@media screen and (max-width: 900px) {
+  .movies-container {
+    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+  }
+
+  .search-sort {
+    flex-direction: column;
+    gap: 10px;
+    align-items: flex-start;
+  }
+
+  .filter__input {
+    width: 100%;
+  }
+}
+
+@media screen and (max-width: 600px) {
+  .movies-container {
+    grid-template-columns: repeat(
+      auto-fill,
+      minmax(100%, 1fr)
+    ); /* One per row */
+  }
+}
diff --git a/styles/movie-card.css b/styles/movie-card.css
deleted file mode 100644
index f8c3a60..0000000
--- a/styles/movie-card.css
+++ /dev/null
@@ -1,183 +0,0 @@
-@import url("https://fonts.googleapis.com/css2?family=Raleway:wght@300;400;600;700&display=swap");
-
-body {
-  background-color: #ffffff;
-  font-family: "Raleway", sans-serif;
-  margin: 0;
-  padding: 0;
-  display: flex;
-  justify-content: center;
-}
-
-#movies-container {
-  max-width: 800px;
-  width: 90%;
-  background: #ffffff;
-  padding: 20px;
-  border-radius: 10px;
-  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
-}
-
-.movie-card {
-  display: flex;
-  background: #cfd8dc;
-  border-radius: 10px;
-  padding: 20px;
-  margin-bottom: 20px;
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
-  flex-wrap: wrap;
-}
-
-.movie-poster {
-  width: 150px;
-  height: auto;
-  border-radius: 10px;
-}
-
-.movie-info {
-  margin-left: 20px;
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-}
-
-.movie-title {
-  font-size: 24px;
-  font-weight: bold;
-  margin-bottom: 5px;
-}
-
-.movie-date {
-  font-size: 16px;
-  color: #455a64;
-  margin-bottom: 10px;
-}
-
-.movie-description {
-  font-size: 15px;
-  color: #383838;
-  margin-bottom: 10px;
-}
-
-.movie-price {
-  font-size: 16px;
-  font-weight: bold;
-  color: #808283;
-  margin-top: auto;
-}
-
-.movie-price-number {
-  color: #455a64;
-  font-weight: bold;
-}
-
-.movie-director {
-  font-size: 15px;
-  color: #202020;
-  margin-top: 10px;
-}
-
-.actors-section {
-  display: flex;
-  flex-wrap: wrap;
-  justify-content: flex-start;
-  gap: 10px;
-  padding: 15px 0;
-  background: #ffffff;
-}
-
-.actor-badge {
-  background: #0d47a1;
-  color: white;
-  font-size: 9px;
-  padding: 4px 5px;
-  border-radius: 20px;
-  display: inline-block;
-}
-
-.comment-section {
-  margin-top: 20px;
-  padding: 20px;
-  background: #f5f5f5;
-  border-radius: 10px;
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-}
-
-.comment-title {
-  font-size: 18px;
-  font-weight: bold;
-  margin-bottom: 10px;
-  color: #333;
-}
-
-.comment-textarea {
-  width: 100%;
-  height: 80px;
-  padding: 10px;
-  border: 1px solid #90a4ae;
-  border-radius: 5px;
-  font-size: 14px;
-  resize: none;
-  display: block;
-}
-
-.comment-form {
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
-}
-
-.comment-btn-wrapper {
-  display: flex;
-  justify-content: flex-end;
-}
-
-.comment-btn {
-  padding: 10px 15px;
-  background: #032541;
-  color: white;
-  border: none;
-  border-radius: 5px;
-  cursor: pointer;
-  font-size: 14px;
-}
-
-.comment-btn:hover {
-  background: #032541;
-}
-
-.comment-list {
-  list-style: none;
-  padding: 0;
-  margin-top: 10px;
-}
-
-.comment-list li {
-  background: #e0e0e0;
-  padding: 8px;
-  border-radius: 5px;
-  margin-top: 5px;
-  font-size: 14px;
-}
-
-@media screen and (max-width: 600px) {
-  .movie-card {
-    flex-direction: column;
-    align-items: center;
-    text-align: center;
-  }
-
-  .movie-poster {
-    width: 100%;
-    max-width: 250px;
-    margin-bottom: 10px;
-  }
-
-  .movie-info {
-    margin-left: 0;
-  }
-
-  .actors-section {
-    justify-content: center;
-  }
-}


Reviews:
user: login: jason-vasilev
body: Super nice code. I ran the app with Live Server, and it's great. Love the filtering options through oldest/newest movies first, alphabetic, or by genre.
There were a few places where I could see you can improve, but nothing critical. You are welcome to either implement those suggestions or to merge.
state: APPROVED
author_association: NONE
submitted_at: 2025-03-15T20:12:50Z

Review comments:
diff_hunk: @@ -4,10 +4,13 @@
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>Movie Reviews</title>
-    <link rel="stylesheet" href="./styles/movie-card.css" />
+    <link rel="stylesheet" href="./styles/app.css" />
   </head>
   <body>
-    <div class="movies-container" id="movies-container"></div>
-    <script src="./scripts/movie-card.js"></script>
+    <div class="main-content">
path: index.html
user: login: jason-vasilev
body: You can use the `<main>` tag here instead of a `<div>`
created_at: 2025-03-15T18:39:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,82 @@
+const movies = [
path: scripts/data.js
user: login: jason-vasilev
body: ```suggestion
export const movies = [
```
It's a good practice to export at the end of the file, but when it's only one variable/function it's alright to do it directly where you define it.
created_at: 2025-03-15T18:54:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,169 @@
+body {
+  background-color: #ffffff;
+  font-family: "Raleway", sans-serif;
+  margin: 0;
+  padding: 0;
+  display: flex;
+  justify-content: center;
path: styles/app.css
user: login: jason-vasilev
body: Does properties don't take any effect and can be deleted.
created_at: 2025-03-15T18:55:02Z
author_association: NONE
diff_hunk: @@ -0,0 +1,169 @@
+body {
+  background-color: #ffffff;
path: styles/app.css
user: login: jason-vasilev
body: This is the browser default background - so no need to specify it.
created_at: 2025-03-15T18:55:46Z
author_association: NONE
diff_hunk: @@ -0,0 +1,169 @@
+body {
+  background-color: #ffffff;
+  font-family: "Raleway", sans-serif;
+  margin: 0;
+  padding: 0;
+  display: flex;
+  justify-content: center;
+}
+
+.main-content {
+  max-width: 1000px;
+  margin: auto;
+  padding: 20px;
+}
+
+.search-sort {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  flex-wrap: wrap;
+}
+
+.search-wrapper,
+.sort-wrapper,
+.genre-wrapper {
path: styles/app.css
user: login: jason-vasilev
body: Instead of creating 3 separate classes you can make one called `.filter__wrapper`
created_at: 2025-03-15T18:57:44Z
author_association: NONE
diff_hunk: @@ -0,0 +1,169 @@
+body {
+  background-color: #ffffff;
+  font-family: "Raleway", sans-serif;
+  margin: 0;
+  padding: 0;
+  display: flex;
+  justify-content: center;
+}
+
+.main-content {
+  max-width: 1000px;
+  margin: auto;
+  padding: 20px;
+}
+
+.search-sort {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  flex-wrap: wrap;
+}
+
+.search-wrapper,
+.sort-wrapper,
+.genre-wrapper {
+  display: flex;
+  flex-direction: column;
+  margin-right: 10px;
+}
+
+label {
+  font-size: 14px;
+  font-weight: bold;
+  margin-bottom: 5px;
+}
+
+#search-input,
+#sort-select,
+#genre-select {
path: styles/app.css
user: login: jason-vasilev
body: Avoid styling on ID - https://medium.com/@clairecodes/reasons-not-to-use-ids-in-css-a42204fb0d97 Instead make a class called something like  `.filter__input`
created_at: 2025-03-15T18:59:14Z
author_association: NONE
diff_hunk: @@ -0,0 +1,86 @@
+import { movies } from "./data.js";
+import { createCommentSection } from "./comments.js";
+
+function createSearchAndSortBar() {
+  const mainContent = document.querySelector(".main-content");
path: scripts/render.js
user: login: jason-vasilev
body: It's a common practice to have `id="main"` on the `<main>` tag. If you'd do that, then you can use here `document.getElementById('main')` instead.
created_at: 2025-03-15T19:01:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,86 @@
+import { movies } from "./data.js";
+import { createCommentSection } from "./comments.js";
+
+function createSearchAndSortBar() {
+  const mainContent = document.querySelector(".main-content");
+  const searchSortContainer = document.createElement("div");
+  searchSortContainer.classList.add("search-sort");
+
+  const genres = [
+    ...new Set(movies.flatMap((movie) => movie.genre.split(", "))),
+  ];
+
+  searchSortContainer.innerHTML = `
+    <div class="search-wrapper">
+      <label for="search-input">Search Movies / Actors:</label>
+      <input type="text" id="search-input" placeholder="Search by title or actor..." />
+    </div>
+    <div class="sort-wrapper">
+      <label for="sort-select">Sort Movies:</label>
+      <select id="sort-select">
+        <option value="title-asc">Title (A-Z)</option>
+        <option value="title-desc">Title (Z-A)</option>
+        <option value="year-asc">Year (Oldest First)</option>
+        <option value="year-desc">Year (Newest First)</option>
+      </select>
+    </div>
+    <div class="genre-wrapper">
+      <label for="genre-select">Filter by Genre:</label>
+      <select id="genre-select">
+        <option value="all">All Genres</option>
+        ${genres
+          .map((genre) => `<option value="${genre}">${genre}</option>`)
+          .join("")}
path: scripts/render.js
user: login: jason-vasilev
body: Nice and tidy!
created_at: 2025-03-15T19:04:00Z
author_association: NONE
diff_hunk: @@ -0,0 +1,86 @@
+import { movies } from "./data.js";
+import { createCommentSection } from "./comments.js";
+
+function createSearchAndSortBar() {
+  const mainContent = document.querySelector(".main-content");
+  const searchSortContainer = document.createElement("div");
+  searchSortContainer.classList.add("search-sort");
+
+  const genres = [
+    ...new Set(movies.flatMap((movie) => movie.genre.split(", "))),
+  ];
+
+  searchSortContainer.innerHTML = `
+    <div class="search-wrapper">
+      <label for="search-input">Search Movies / Actors:</label>
+      <input type="text" id="search-input" placeholder="Search by title or actor..." />
+    </div>
+    <div class="sort-wrapper">
+      <label for="sort-select">Sort Movies:</label>
+      <select id="sort-select">
+        <option value="title-asc">Title (A-Z)</option>
+        <option value="title-desc">Title (Z-A)</option>
+        <option value="year-asc">Year (Oldest First)</option>
+        <option value="year-desc">Year (Newest First)</option>
+      </select>
+    </div>
+    <div class="genre-wrapper">
+      <label for="genre-select">Filter by Genre:</label>
+      <select id="genre-select">
+        <option value="all">All Genres</option>
+        ${genres
+          .map((genre) => `<option value="${genre}">${genre}</option>`)
+          .join("")}
+      </select>
+    </div>
+  `;
+
+  mainContent.insertBefore(
+    searchSortContainer,
+    document.getElementById("movies-container")
+  );
+}
+
+function renderMovies(movieList) {
+  const moviesContainer = document.getElementById("movies-container");
+  moviesContainer.innerHTML = "";
+
+  movieList.forEach((movie) => {
+    const movieCard = createMovieCard(movie);
+    moviesContainer.appendChild(movieCard);
+  });
+}
+
+function createMovieCard(movie) {
+  const movieCard = document.createElement("div");
+  movieCard.classList.add("movie-card");
+
+  movieCard.innerHTML = `
+    <img src="${movie.poster_url}" alt="${movie.title}" class="movie-poster">
+    <div class="movie-info">
+      <h2 class="movie-title">${movie.title}</h2>
+      <p class="movie-date"><strong>Released:</strong> ${movie.movie_year}</p>
+      <p class="movie-genre"><strong>Genre:</strong> ${movie.genre
+        .split(", ")
+        .map((genre) => `<span class="badge">${genre}</span>`)
path: scripts/render.js
user: login: jason-vasilev
body: The name of such elements is more often called `.pill` instead of `.badge`, but that's a nitpick that you can dismiss.
Otherwise very nice approach to handling comma-separated lists with split, map and join.
created_at: 2025-03-15T19:12:34Z
author_association: NONE
diff_hunk: @@ -0,0 +1,59 @@
+function createCommentSection(movieId) {
+  return `
+    <div class="comment-section" id="comment-section-${movieId}" style="display: none;">
+      <form class="comment-form">
+        <textarea id="comment-${movieId}" class="comment-textarea" placeholder="Write your comment here..."></textarea>
+        <button type="submit" class="comment-btn" data-movie-id="${movieId}">Submit</button>
+      </form>
+      <ul id="comment-list-${movieId}" class="comment-list"></ul>
+    </div>
+  `;
+}
+
+// Handles toggling the visibility of the comment section.
+function handleCommentToggle(event) {
+  if (event.target.classList.contains("toggle-comment")) {
+    const movieId = event.target.getAttribute("data-movie-id");
+    const commentSection = document.getElementById(
+      `comment-section-${movieId}`
+    );
+
+    // Hide all other comment sections
+    document.querySelectorAll(".comment-section").forEach((section) => {
+      if (section.id !== `comment-section-${movieId}`) {
+        section.style.display = "none";
path: scripts/comments.js
user: login: jason-vasilev
body: Now you have an issue where you hide previously open comment boxes, but the buttons still say "Hide comments". To fix this you can add the following little reset:
```
section.previousElementSibling.innerHTML = "Show Comments";
```

Generally it's not a good practice to rely on selectors as `previousElementSibling` but there isn't really a better way here.
created_at: 2025-03-15T20:03:01Z
author_association: NONE
diff_hunk: @@ -0,0 +1,39 @@
+import { movies } from "./data.js";
+import { renderMovies } from "./render.js";
+
+// Filters and sorts movies based on user input.
+function filterAndSortMovies() {
+  const searchQuery = document
+    .getElementById("search-input")
+    .value.toLowerCase();
+  const sortOption = document.getElementById("sort-select").value;
+  const selectedGenre = document.getElementById("genre-select").value;
+
+  let filteredMovies = movies.filter(
+    (movie) =>
+      (movie.title.toLowerCase().includes(searchQuery) ||
+        movie.actors.some((actor) =>
+          actor.toLowerCase().includes(searchQuery)
+        )) &&
+      (selectedGenre === "all" || movie.genre.includes(selectedGenre))
+  );
+
+  switch (sortOption) {
+    case "title-asc":
+      filteredMovies.sort((a, b) => a.title.localeCompare(b.title));
+      break;
+    case "title-desc":
+      filteredMovies.sort((a, b) => b.title.localeCompare(a.title));
+      break;
+    case "year-asc":
+      filteredMovies.sort((a, b) => a.movie_year - b.movie_year);
+      break;
+    case "year-desc":
+      filteredMovies.sort((a, b) => b.movie_year - a.movie_year);
+      break;
+  }
+
+  renderMovies(filteredMovies);
path: scripts/filters.js
user: login: jason-vasilev
body: Good to add here a message informing if there are no movies found with the search string:
```

  if (filteredMovies.length === 0) {
    const moviesContainer = document.getElementById("movies-container");
    moviesContainer.innerHTML = "<p>No movies found</p>";
    return;
  }

  renderMovies(filteredMovies);
```
created_at: 2025-03-15T20:08:33Z
author_association: NONE
diff_hunk: @@ -0,0 +1,39 @@
+import { movies } from "./data.js";
+import { renderMovies } from "./render.js";
+
+// Filters and sorts movies based on user input.
+function filterAndSortMovies() {
+  const searchQuery = document
+    .getElementById("search-input")
+    .value.toLowerCase();
+  const sortOption = document.getElementById("sort-select").value;
+  const selectedGenre = document.getElementById("genre-select").value;
+
+  let filteredMovies = movies.filter(
path: scripts/filters.js
user: login: jason-vasilev
body: Could be set as `const`
created_at: 2025-03-15T20:10:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,39 @@
+import { movies } from "./data.js";
+import { renderMovies } from "./render.js";
+
+// Filters and sorts movies based on user input.
+function filterAndSortMovies() {
+  const searchQuery = document
+    .getElementById("search-input")
+    .value.toLowerCase();
+  const sortOption = document.getElementById("sort-select").value;
+  const selectedGenre = document.getElementById("genre-select").value;
+
+  let filteredMovies = movies.filter(
+    (movie) =>
+      (movie.title.toLowerCase().includes(searchQuery) ||
+        movie.actors.some((actor) =>
+          actor.toLowerCase().includes(searchQuery)
+        )) &&
+      (selectedGenre === "all" || movie.genre.includes(selectedGenre))
+  );
path: scripts/filters.js
user: login: jason-vasilev
body: You do process a lot here. It could be nice to either simplify it in a way, or add a comment explaining what's happening.
created_at: 2025-03-15T20:10:56Z
author_association: NONE

PR comments:
user: login: jason-vasilev
created_at: 2025-03-15T20:14:29Z
body: Forgot to say that the PR description was very helpful and even more helpful with the extensive README.md file.
author_association: NONE
user: login: netlify[bot]
created_at: 2025-03-16T20:14:41Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *moviapplication* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 0d2bd3b8f41207a14f5fae8715b981f4cc4e4b1e |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/moviapplication/deploys/67d7312499f2cb0008e8872b |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-2--moviapplication.netlify.app](https://deploy-preview-2--moviapplication.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTItLW1vdmlhcHBsaWNhdGlvbi5uZXRsaWZ5LmFwcCJ9.CEcp7pqVPbNhUwKRs5B7EE_DqM0toBgANEpS9oOstQQ)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [moviapplication Preview](https://deploy-preview-2--moviapplication.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/moviapplication/configuration/notifications#deploy-webhooks)._
author_association: NONE