ChandimaPrabath commited on
Commit
c9869b0
β€’
1 Parent(s): fcb32f7

0.0.0.6 Alpha

Browse files
Files changed (47) hide show
  1. .gitignore +3 -0
  2. frontend/public/deep-sea-abyss.jpg +0 -0
  3. frontend/src/app/_error.js +12 -7
  4. frontend/src/app/_still-in-development.js +68 -0
  5. frontend/src/app/genres/[genre]/genres.css +13 -57
  6. frontend/src/app/genres/[genre]/page.js +60 -111
  7. frontend/src/app/globals.css +10 -11
  8. frontend/src/app/index.css +4 -0
  9. frontend/src/app/layout.js +6 -8
  10. frontend/src/app/movie/[title]/movie.css +13 -12
  11. frontend/src/app/movie/[title]/page.js +3 -3
  12. frontend/src/app/movies/filmsPage.css +7 -10
  13. frontend/src/app/movies/page.js +35 -35
  14. frontend/src/app/not-found.js +9 -8
  15. frontend/src/app/page.js +8 -5
  16. frontend/src/app/player/movie/[title]/MoviePlayerPage.css +10 -0
  17. frontend/src/app/player/movie/[title]/page.js +1 -1
  18. frontend/src/app/search/page.js +15 -70
  19. frontend/src/app/search/searchPage.css +2 -73
  20. frontend/src/app/tvshows/page.js +2 -2
  21. frontend/src/components/MovieCard.css +0 -83
  22. frontend/src/components/{MoviePlayer.css β†’ Player/Movie/MoviePlayer.css} +7 -0
  23. frontend/src/components/{MoviePlayer.js β†’ Player/Movie/MoviePlayer.js} +3 -2
  24. frontend/src/components/shared/Card/Card.css +113 -0
  25. frontend/src/components/{MovieCard.js β†’ shared/Card/MovieCard.js} +23 -20
  26. frontend/src/components/{TvShowCard.js β†’ shared/Card/TvShowCard.js} +2 -2
  27. frontend/src/components/{Header.css β†’ shared/Header/Header.css} +0 -0
  28. frontend/src/components/{Header.js β†’ shared/Header/Header.js} +1 -12
  29. frontend/src/components/{SeekableProgressBar.css β†’ shared/ProgressBar/SeekableProgressBar.css} +0 -0
  30. frontend/src/components/{SeekableProgressBar.js β†’ shared/ProgressBar/SeekableProgressBar.js} +0 -0
  31. frontend/src/components/{CastSection.css β†’ shared/Sections/CastSection.css} +0 -0
  32. frontend/src/components/{CastSection.js β†’ shared/Sections/CastSection.js} +0 -0
  33. frontend/src/components/{HeroSection.css β†’ shared/Sections/HeroSection.css} +14 -10
  34. frontend/src/components/{HeroSection.js β†’ shared/Sections/HeroSection.js} +5 -1
  35. frontend/src/components/shared/Sections/ScrollSection.css +87 -0
  36. frontend/src/components/shared/Sections/ScrollSection.js +79 -0
  37. frontend/src/components/{Sidebar.css β†’ shared/Sidebar/Sidebar.css} +0 -0
  38. frontend/src/components/{Sidebar.js β†’ shared/Sidebar/Sidebar.js} +0 -0
  39. frontend/src/components/{Spinner.css β†’ shared/Spinner/Spinner.css} +0 -0
  40. frontend/src/components/{Spinner.js β†’ shared/Spinner/Spinner.js} +0 -0
  41. frontend/src/lib/config.js +1 -1
  42. frontend/src/modals/{GenreFilterModal.css β†’ Genre/GenreFilterModal.css} +0 -0
  43. frontend/src/modals/{GenreFilterModal.js β†’ Genre/GenreFilterModal.js} +0 -0
  44. frontend/src/skeletons/{movieCard.css β†’ Card/movieCard.css} +4 -5
  45. frontend/src/skeletons/{movieCard.js β†’ Card/movieCard.js} +1 -1
  46. frontend/src/skeletons/{HeroSection.css β†’ Sections/HeroSection.css} +6 -5
  47. frontend/src/skeletons/{HeroSection.js β†’ Sections/HeroSection.js} +0 -1
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ frontend/public/sub.srt
2
+ frontend/public/sub.vtt
3
+ frontend/public/My.Spy.The.Eternal.City.(2024).WEB.vtt
frontend/public/deep-sea-abyss.jpg ADDED
frontend/src/app/_error.js CHANGED
@@ -4,7 +4,8 @@ import Link from "next/link";
4
  export default function ErrorPage({ errorMessage }) {
5
  return (
6
  <div style={styles.container}>
7
- <h1 style={styles.title}>Something Went Wrong</h1>
 
8
  <p style={styles.message}>{errorMessage}</p>
9
  <Link href="/" style={styles.link}>
10
  Go Home
@@ -21,8 +22,8 @@ const styles = {
21
  alignItems: 'center',
22
  justifyContent: 'center',
23
  height: '100dvh',
24
- width:'100dvw',
25
- top:'0',
26
  textAlign: 'center',
27
  color: 'white',
28
  padding: '20px',
@@ -34,23 +35,27 @@ const styles = {
34
  left: 0,
35
  right: 0,
36
  bottom: 0,
37
- background: 'url("/404_bg.jpg") no-repeat center center/cover',
38
  filter: "blur(5px)",
39
- zIndex: -1, // Sends the overlay behind the text
40
  },
41
  title: {
 
42
  fontSize: "3rem",
43
  fontWeight: "bold",
44
- color: "#ff4747",
 
45
  },
46
  message: {
47
  fontSize: "1.5rem",
48
  margin: "20px 0",
 
 
49
  },
50
  link: {
51
  marginTop: "20px",
52
  padding: "12px 20px",
53
- backgroundColor: "var(--bg-primary)", // Semi-transparent red
54
  color: "white",
55
  textDecoration: "none",
56
  borderRadius: "5px",
 
4
  export default function ErrorPage({ errorMessage }) {
5
  return (
6
  <div style={styles.container}>
7
+ <div style={styles.overlay}></div>
8
+ <h1 style={styles.title}>Uh-oh! 🚨</h1>
9
  <p style={styles.message}>{errorMessage}</p>
10
  <Link href="/" style={styles.link}>
11
  Go Home
 
22
  alignItems: 'center',
23
  justifyContent: 'center',
24
  height: '100dvh',
25
+ width: '100vw',
26
+ top: '0',
27
  textAlign: 'center',
28
  color: 'white',
29
  padding: '20px',
 
35
  left: 0,
36
  right: 0,
37
  bottom: 0,
38
+ background: 'url("/glich_bg.jpg") no-repeat center center/cover',
39
  filter: "blur(5px)",
40
+ zIndex: -1,
41
  },
42
  title: {
43
+ color: 'var(--primary-special-color)',
44
  fontSize: "3rem",
45
  fontWeight: "bold",
46
+ textShadow: "2px 2px 4px rgba(0, 0, 0, 0.7)",
47
+ marginBottom: "20px",
48
  },
49
  message: {
50
  fontSize: "1.5rem",
51
  margin: "20px 0",
52
+ fontWeight: "300",
53
+ textShadow: "1px 1px 3px rgba(0, 0, 0, 0.7)",
54
  },
55
  link: {
56
  marginTop: "20px",
57
  padding: "12px 20px",
58
+ backgroundColor: "var(--bg-primary)",
59
  color: "white",
60
  textDecoration: "none",
61
  borderRadius: "5px",
frontend/src/app/_still-in-development.js ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Link from "next/link";
2
+
3
+ export default function StillInDevelopment() {
4
+ return (
5
+ <div style={styles.container}>
6
+ <div style={styles.overlay}></div>
7
+ <h2 style={styles.title}>Lost in Dev Space 🀨</h2>
8
+ <p style={styles.message}>
9
+ Oops! This page is still under construction. <br />
10
+ Our devs are probably chasingπŸͺ²bugs or having a coffee break β˜•. <br />
11
+ In the meantime, click below to escape!πŸƒπŸšͺ
12
+ </p>
13
+ <Link href="/" style={styles.link}>
14
+ Take Me 🏚️Home
15
+ </Link>
16
+ </div>
17
+ );
18
+ }
19
+
20
+ const styles = {
21
+ container: {
22
+ position: "absolute",
23
+ display: "flex",
24
+ flexDirection: "column",
25
+ alignItems: "center",
26
+ justifyContent: "center",
27
+ height: "100dvh",
28
+ width: "100dvw",
29
+ top: "0",
30
+ textAlign: "center",
31
+ color: "white",
32
+ padding: "20px",
33
+ overflow: "hidden",
34
+ },
35
+ overlay: {
36
+ position: "absolute",
37
+ top: 0,
38
+ left: 0,
39
+ right: 0,
40
+ bottom: 0,
41
+ background: 'url("/404_bg3.jpg") no-repeat center center/cover',
42
+ filter: "blur(10px)",
43
+ zIndex: -1,
44
+ },
45
+ title: {
46
+ fontSize: "2.5rem",
47
+ fontWeight: "900",
48
+ textShadow: "2px 2px 4px rgba(0, 0, 0, 0.7)",
49
+ marginBottom: "20px",
50
+ color: "var(--primary-special-color)",
51
+ },
52
+ message: {
53
+ fontSize: "1.2rem",
54
+ marginBottom: "20px",
55
+ fontWeight: "400",
56
+ },
57
+ link: {
58
+ padding: "10px 20px",
59
+ backgroundColor: "var(--bg-primary)",
60
+ color: "white",
61
+ textDecoration: "none",
62
+ borderRadius: "5px",
63
+ border: "2px solid var(--primary-special-color)",
64
+ transition: "background-color 0.3s ease",
65
+ boxShadow: "0 4px 8px rgba(0, 0, 0, 0.5)",
66
+ fontWeight: "200",
67
+ },
68
+ };
frontend/src/app/genres/[genre]/genres.css CHANGED
@@ -3,57 +3,12 @@ body {
3
  }
4
  /* Container for the genre page */
5
  .genre-page {
6
- max-width: 100dvw;
7
  margin: 0 auto;
8
  padding: 20px;
9
- background-color: #0a0b19; /* Slightly lighter dark background */
10
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
11
  }
12
 
13
- .genre-title-section {
14
- display: flex;
15
- justify-content: center;
16
- align-items: center;
17
- }
18
-
19
- /* Section styling */
20
- .section {
21
- margin-bottom: 40px;
22
- }
23
-
24
- @media (orientation: landscape) {
25
- .section {
26
- margin-left: 25px;
27
- margin-right: 25px;
28
- }
29
- }
30
-
31
- .section h2 {
32
- font-size: 1.8rem;
33
- color: #cccccc; /* Lighter gray for section titles */
34
- }
35
-
36
- /* Horizontal scrolling container */
37
- .genre-items-grid {
38
- display: flex;
39
- overflow-x: auto;
40
- padding: 10px 0;
41
- gap: 20px;
42
- }
43
-
44
- .genre-items-grid::-webkit-scrollbar {
45
- height: 8px;
46
- }
47
-
48
- .genre-items-grid::-webkit-scrollbar-thumb {
49
- background-color: #3c23cf;
50
- border-radius: 4px;
51
- }
52
-
53
- .genre-items-grid::-webkit-scrollbar-track {
54
- background-color: #2c2c2c;
55
- }
56
-
57
  /* Card for each item */
58
  .genre-item-card {
59
  background-color: #1d1f2d; /* Dark card background */
@@ -64,6 +19,10 @@ body {
64
  flex: 0 0 200px; /* Fixed width for horizontal scrolling */
65
  }
66
 
 
 
 
 
67
  .genre-item-card:hover {
68
  transform: scale(1.03);
69
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.7);
@@ -160,21 +119,18 @@ body {
160
  outline: none;
161
  }
162
 
163
- .genre-section-controls {
164
- display: flex;
165
- align-items: center;
166
- text-align: center;
167
- justify-content: space-between;
168
- }
169
-
170
- .genre-scroll-controls button {
171
- margin-left: 10px;
172
  margin-right: 10px;
 
 
 
 
173
  }
174
 
175
- .genre-filter-button {
176
- margin-right: 10px;
177
  }
 
178
  .genre-bubbles-scroll-section{
179
  display: flex;
180
  overflow-x: auto;
 
3
  }
4
  /* Container for the genre page */
5
  .genre-page {
6
+ width: 100dvw;
7
  margin: 0 auto;
8
  padding: 20px;
 
9
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
10
  }
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  /* Card for each item */
13
  .genre-item-card {
14
  background-color: #1d1f2d; /* Dark card background */
 
19
  flex: 0 0 200px; /* Fixed width for horizontal scrolling */
20
  }
21
 
22
+ .genre-item-card a{
23
+ text-decoration: none;
24
+ }
25
+
26
  .genre-item-card:hover {
27
  transform: scale(1.03);
28
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.7);
 
119
  outline: none;
120
  }
121
 
122
+ .genre-filter-button {
 
 
 
 
 
 
 
 
123
  margin-right: 10px;
124
+ padding: 10px;
125
+ min-width: 45px;
126
+ border-radius: 50%;
127
+ transition: background-color .3s ease;
128
  }
129
 
130
+ .genre-filter-button:hover {
131
+ background-color: var(--primary-special-color);
132
  }
133
+
134
  .genre-bubbles-scroll-section{
135
  display: flex;
136
  overflow-x: auto;
frontend/src/app/genres/[genre]/page.js CHANGED
@@ -1,19 +1,14 @@
1
  "use client";
2
  import apiClient from "@/api/apiClient";
3
- import { useEffect, useState, useRef } from "react";
4
  import Link from "next/link";
5
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6
- import {
7
- faCaretLeft,
8
- faCaretRight,
9
- faFilter,
10
- faTimes,
11
- faXmark, // Import the close icon
12
- } from "@fortawesome/free-solid-svg-icons";
13
  import Image from "next/image";
14
  import "./genres.css";
15
- import GenreFilterModal from "@/modals/GenreFilterModal"; // Import the Modal
16
- import { Spinner } from "@/components/Spinner";
 
17
 
18
  export default function GenrePage({ params }) {
19
  const [genreItems, setGenreItems] = useState(null);
@@ -23,10 +18,7 @@ export default function GenrePage({ params }) {
23
  const [selectedGenres, setSelectedGenres] = useState(
24
  new Set(params.genre ? decodeURIComponent(params.genre).split(",") : [])
25
  );
26
- const [isModalOpen, setIsModalOpen] = useState(false); // State for modal visibility
27
-
28
- const moviesRef = useRef(null);
29
- const seriesRef = useRef(null);
30
 
31
  useEffect(() => {
32
  async function fetchData() {
@@ -59,13 +51,6 @@ export default function GenrePage({ params }) {
59
  setItemLimit((prevLimit) => prevLimit + 5);
60
  };
61
 
62
- const scroll = (ref, direction) => {
63
- if (ref.current) {
64
- const scrollAmount = direction === "left" ? -360 : 360;
65
- ref.current.scrollBy({ left: scrollAmount, behavior: "smooth" });
66
- }
67
- };
68
-
69
  const openModal = () => setIsModalOpen(true);
70
  const closeModal = () => setIsModalOpen(false);
71
 
@@ -86,62 +71,45 @@ export default function GenrePage({ params }) {
86
  }
87
 
88
  return (
89
- <div className="genre-page">
90
- <div className="genre-title-section"></div>
91
- <div className="genre-bubbles">
92
- <button className="genre-filter-button" onClick={openModal}>
93
- <FontAwesomeIcon icon={faFilter} size="sm" />
94
- </button>
95
- <div className="genre-bubbles-scroll-section">
96
- {selectedGenres.size > 0
97
- ? Array.from(selectedGenres).map((genre, index) => (
98
- <div key={index} className="bubbles">
99
- {genre}
100
- <button
101
- className="genre-bubble-close-button"
102
- onClick={() => removeGenre(genre)}
103
- >
104
- <FontAwesomeIcon icon={faXmark} size="sm" />
105
- </button>
106
- </div>
107
- ))
108
- : "Select Genres"}
109
- </div>
110
- </div>
111
- <GenreFilterModal
112
- isOpen={isModalOpen}
113
- onClose={closeModal}
114
- selectedGenres={selectedGenres}
115
- onGenreChange={setSelectedGenres}
116
- />
117
-
118
- {genreItems && (
119
- <>
120
- {genreItems.movies && genreItems.movies.length > 0 && (
121
- <section className="genre-section movies">
122
- <div className="genre-section-controls">
123
- <h2>Movies</h2>
124
- <div className="load-more-container">
125
  <button className="load-more-button" onClick={loadMore}>
126
  Load More
127
  </button>
128
  </div>
129
- <div className="genre-scroll-controls">
130
- <button
131
- onClick={() => scroll(moviesRef, "left")}
132
- className="genre-scroll-button"
133
- >
134
- <FontAwesomeIcon icon={faCaretLeft} size="2xl" />
135
- </button>
136
- <button
137
- onClick={() => scroll(moviesRef, "right")}
138
- className="genre-scroll-button"
139
- >
140
- <FontAwesomeIcon icon={faCaretRight} size="2xl" />
141
- </button>
142
- </div>
143
- </div>
144
- <div className="genre-items-grid" ref={moviesRef}>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  {genreItems.movies.map((item, index) => (
146
  <div key={index} className="genre-item-card">
147
  <Link href={`/movie/${item[0]}`} passHref>
@@ -149,14 +117,14 @@ export default function GenrePage({ params }) {
149
  <Image
150
  src={
151
  item[3] ||
152
- `https://via.placeholder.com/300x80/1a1c3f/FFF?text=${encodeURIComponent(
153
  item[0]
154
  )}`
155
  }
156
  alt={item[0]}
157
  className="genre-item-image"
158
- height={80}
159
  width={300}
 
160
  />
161
  <div className="genre-item-info">
162
  <h3 className="genre-item-title">{item[0]}</h3>
@@ -166,30 +134,11 @@ export default function GenrePage({ params }) {
166
  </Link>
167
  </div>
168
  ))}
169
- </div>
170
- </section>
171
- )}
172
 
173
- {genreItems.series && genreItems.series.length > 0 && (
174
- <section className="genre-section series">
175
- <div className="genre-section-controls">
176
- <h2>TV Shows</h2>
177
- <div className="genre-scroll-controls">
178
- <button
179
- onClick={() => scroll(seriesRef, "left")}
180
- className="genre-scroll-button"
181
- >
182
- <FontAwesomeIcon icon={faCaretLeft} size="2xl" />
183
- </button>
184
- <button
185
- onClick={() => scroll(seriesRef, "right")}
186
- className="genre-scroll-button"
187
- >
188
- <FontAwesomeIcon icon={faCaretRight} size="2xl" />
189
- </button>
190
- </div>
191
- </div>
192
- <div className="genre-items-grid" ref={seriesRef}>
193
  {genreItems.series.map((item, index) => (
194
  <div key={index} className="genre-item-card">
195
  <Link href={`/series/${item[0]}`} passHref>
@@ -214,20 +163,20 @@ export default function GenrePage({ params }) {
214
  </Link>
215
  </div>
216
  ))}
217
- </div>
218
- </section>
219
- )}
220
- </>
221
- )}
222
 
223
- {(!genreItems ||
224
- (genreItems.movies.length === 0 && genreItems.series.length === 0)) && (
225
- <p className="genre-no-items">
226
- {selectedGenres.size === 0
227
- ? "Please select a genre to see results."
228
- : "No items available for the selected genres."}
229
- </p>
230
- )}
 
231
  </div>
232
  );
233
  }
 
1
  "use client";
2
  import apiClient from "@/api/apiClient";
3
+ import { useEffect, useState } from "react";
4
  import Link from "next/link";
5
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6
+ import { faFilter, faXmark } from "@fortawesome/free-solid-svg-icons";
 
 
 
 
 
 
7
  import Image from "next/image";
8
  import "./genres.css";
9
+ import GenreFilterModal from "@/modals/Genre/GenreFilterModal";
10
+ import { Spinner } from "@/components/shared/Spinner/Spinner";
11
+ import ScrollSection from "@/components/shared/Sections/ScrollSection";
12
 
13
  export default function GenrePage({ params }) {
14
  const [genreItems, setGenreItems] = useState(null);
 
18
  const [selectedGenres, setSelectedGenres] = useState(
19
  new Set(params.genre ? decodeURIComponent(params.genre).split(",") : [])
20
  );
21
+ const [isModalOpen, setIsModalOpen] = useState(false);
 
 
 
22
 
23
  useEffect(() => {
24
  async function fetchData() {
 
51
  setItemLimit((prevLimit) => prevLimit + 5);
52
  };
53
 
 
 
 
 
 
 
 
54
  const openModal = () => setIsModalOpen(true);
55
  const closeModal = () => setIsModalOpen(false);
56
 
 
71
  }
72
 
73
  return (
74
+ <div className="app-container">
75
+ <div className="genre-page">
76
+ <div className="genre-title-section"></div>
77
+ <div className="load-more-container">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  <button className="load-more-button" onClick={loadMore}>
79
  Load More
80
  </button>
81
  </div>
82
+ <div className="genre-bubbles">
83
+ <button className="genre-filter-button" onClick={openModal}>
84
+ <FontAwesomeIcon icon={faFilter} size="sm" />
85
+ </button>
86
+ <div className="genre-bubbles-scroll-section">
87
+ {selectedGenres.size > 0
88
+ ? Array.from(selectedGenres).map((genre, index) => (
89
+ <div key={index} className="bubbles">
90
+ {genre}
91
+ <button
92
+ className="genre-bubble-close-button"
93
+ onClick={() => removeGenre(genre)}
94
+ >
95
+ <FontAwesomeIcon icon={faXmark} size="sm" />
96
+ </button>
97
+ </div>
98
+ ))
99
+ : "Select Genres"}
100
+ </div>
101
+ </div>
102
+ <GenreFilterModal
103
+ isOpen={isModalOpen}
104
+ onClose={closeModal}
105
+ selectedGenres={selectedGenres}
106
+ onGenreChange={setSelectedGenres}
107
+ />
108
+
109
+ {genreItems && (
110
+ <>
111
+ {genreItems.movies && genreItems.movies.length > 0 && (
112
+ <ScrollSection title="Movies">
113
  {genreItems.movies.map((item, index) => (
114
  <div key={index} className="genre-item-card">
115
  <Link href={`/movie/${item[0]}`} passHref>
 
117
  <Image
118
  src={
119
  item[3] ||
120
+ `https://via.placeholder.com/300x80?text=${encodeURIComponent(
121
  item[0]
122
  )}`
123
  }
124
  alt={item[0]}
125
  className="genre-item-image"
 
126
  width={300}
127
+ height={80}
128
  />
129
  <div className="genre-item-info">
130
  <h3 className="genre-item-title">{item[0]}</h3>
 
134
  </Link>
135
  </div>
136
  ))}
137
+ </ScrollSection>
138
+ )}
 
139
 
140
+ {genreItems.series && genreItems.series.length > 0 && (
141
+ <ScrollSection title="TV Shows">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  {genreItems.series.map((item, index) => (
143
  <div key={index} className="genre-item-card">
144
  <Link href={`/series/${item[0]}`} passHref>
 
163
  </Link>
164
  </div>
165
  ))}
166
+ </ScrollSection>
167
+ )}
168
+ </>
169
+ )}
 
170
 
171
+ {(!genreItems ||
172
+ (genreItems.movies.length === 0 && genreItems.series.length === 0)) && (
173
+ <p className="genre-no-items">
174
+ {selectedGenres.size === 0
175
+ ? "Please select a genre to see results."
176
+ : "No items available for the selected genres."}
177
+ </p>
178
+ )}
179
+ </div>
180
  </div>
181
  );
182
  }
frontend/src/app/globals.css CHANGED
@@ -8,6 +8,7 @@
8
  --bg-primary: #11121f;
9
  --bg-secondary: #0c0c16;
10
  --primary-special-color:#4343ff;
 
11
  }
12
 
13
  body {
@@ -19,18 +20,16 @@ body {
19
  }
20
 
21
  .app-container {
22
- -ms-overflow-style: none; /* IE and Edge */
23
- scrollbar-width: none; /* Firefox */
24
- scroll-behavior: smooth;
25
- overflow: scroll;
26
- height: 100dvh;
27
- padding-top: 80px;
28
- }
29
-
30
- .no-padding {
31
- padding-top: 0;
32
  }
33
 
34
  .app-container::-webkit-scrollbar {
35
- display: none;
36
  }
 
8
  --bg-primary: #11121f;
9
  --bg-secondary: #0c0c16;
10
  --primary-special-color:#4343ff;
11
+ --gray-text-color:#d8d8d8;
12
  }
13
 
14
  body {
 
20
  }
21
 
22
  .app-container {
23
+ -ms-overflow-style: none !important; /* IE and Edge */
24
+ scrollbar-width: none !important; /* Firefox */
25
+ scroll-behavior: smooth !important;
26
+ overflow: scroll !important;
27
+ height: 100dvh !important;
28
+ padding-top: 80px !important;
29
+ padding-left: 0 !important;
30
+ padding-right: 0 !important;
 
 
31
  }
32
 
33
  .app-container::-webkit-scrollbar {
34
+ display: none !important;
35
  }
frontend/src/app/index.css CHANGED
@@ -14,6 +14,10 @@
14
  flex: 1;
15
  }
16
 
 
 
 
 
17
  .section-title {
18
  color: #ffffff;
19
  font-size: 1.4rem;
 
14
  flex: 1;
15
  }
16
 
17
+ .index-page-content{
18
+ width: 100dvw;
19
+ }
20
+
21
  .section-title {
22
  color: #ffffff;
23
  font-size: 1.4rem;
frontend/src/app/layout.js CHANGED
@@ -3,10 +3,10 @@ import "./globals.css";
3
  import { FilmProvider } from "@/context/FilmContext";
4
  import { config } from "@fortawesome/fontawesome-svg-core";
5
  import "@fortawesome/fontawesome-svg-core/styles.css";
6
- import Header from "@/components/Header";
7
  import { TvShowsProvider } from "@/context/TvshowContext";
8
 
9
- config.autoAddCss = true;
10
  const inter = Inter({ subsets: ["latin"] });
11
 
12
  export default function RootLayout({ children }) {
@@ -14,14 +14,12 @@ export default function RootLayout({ children }) {
14
  <html lang="en">
15
  <body className={inter.className}>
16
  <header>
17
- <link rel="manifest" href="webmanifest.json"/>
18
  <Header />
19
  </header>
20
- <div className="app-container">
21
- <TvShowsProvider>
22
- <FilmProvider>{children}</FilmProvider>
23
- </TvShowsProvider>
24
- </div>
25
  </body>
26
  </html>
27
  );
 
3
  import { FilmProvider } from "@/context/FilmContext";
4
  import { config } from "@fortawesome/fontawesome-svg-core";
5
  import "@fortawesome/fontawesome-svg-core/styles.css";
6
+ import Header from "@/components/shared/Header/Header";
7
  import { TvShowsProvider } from "@/context/TvshowContext";
8
 
9
+ config.autoAddCss = false;
10
  const inter = Inter({ subsets: ["latin"] });
11
 
12
  export default function RootLayout({ children }) {
 
14
  <html lang="en">
15
  <body className={inter.className}>
16
  <header>
17
+ <link rel="manifest" href="webmanifest.json" />
18
  <Header />
19
  </header>
20
+ <TvShowsProvider>
21
+ <FilmProvider>{children}</FilmProvider>
22
+ </TvShowsProvider>
 
 
23
  </body>
24
  </html>
25
  );
frontend/src/app/movie/[title]/movie.css CHANGED
@@ -14,8 +14,9 @@
14
  left: 0;
15
  width: 100%;
16
  height: 100%;
17
- background-size: 250% 150%; /* Adjusted size to create a zooming effect */
18
  background-position: center;
 
19
  z-index: -1;
20
  animation: backdrop-anim-portrait 20s ease-in-out infinite;
21
  filter: blur(10px);
@@ -24,29 +25,29 @@
24
  @keyframes backdrop-anim-portrait {
25
  0% {
26
  background-position: center;
27
- background-size: 350% 150%;
28
  }
29
  25% {
30
  background-position: top left;
31
- background-size: 320% 120%;
32
  }
33
  50% {
34
  background-position: bottom right;
35
- background-size: 350% 150%;
36
  }
37
  75% {
38
  background-position: top right;
39
- background-size: 320% 120%;
40
  }
41
  100% {
42
  background-position: center;
43
- background-size: 350% 150%;
44
  }
45
  }
46
 
47
  @media (orientation: landscape) {
48
  .movie-details-backdrop {
49
- background-size: 250% 150%; /* Adjusted size to create a zooming effect */
50
  animation: backdrop-anim-landscape 20s ease-in-out infinite;
51
  }
52
  }
@@ -54,23 +55,23 @@
54
  @keyframes backdrop-anim-landscape {
55
  0% {
56
  background-position: center;
57
- background-size: 150% 150%;
58
  }
59
  25% {
60
  background-position: top left;
61
- background-size: 120% 120%;
62
  }
63
  50% {
64
  background-position: bottom right;
65
- background-size: 150% 150%;
66
  }
67
  75% {
68
  background-position: top right;
69
- background-size: 120% 120%;
70
  }
71
  100% {
72
  background-position: center;
73
- background-size: 150% 150%;
74
  }
75
  }
76
  /* Container Styling */
 
14
  left: 0;
15
  width: 100%;
16
  height: 100%;
17
+ background-size: 250% ; /* Adjusted size to create a zooming effect */
18
  background-position: center;
19
+ background-repeat: no-repeat;
20
  z-index: -1;
21
  animation: backdrop-anim-portrait 20s ease-in-out infinite;
22
  filter: blur(10px);
 
25
  @keyframes backdrop-anim-portrait {
26
  0% {
27
  background-position: center;
28
+ background-size: 300% ;
29
  }
30
  25% {
31
  background-position: top left;
32
+ background-size: 250% ;
33
  }
34
  50% {
35
  background-position: bottom right;
36
+ background-size: 300% ;
37
  }
38
  75% {
39
  background-position: top right;
40
+ background-size: 2500% ;
41
  }
42
  100% {
43
  background-position: center;
44
+ background-size: 300% ;
45
  }
46
  }
47
 
48
  @media (orientation: landscape) {
49
  .movie-details-backdrop {
50
+ background-size: 250%; /* Adjusted size to create a zooming effect */
51
  animation: backdrop-anim-landscape 20s ease-in-out infinite;
52
  }
53
  }
 
55
  @keyframes backdrop-anim-landscape {
56
  0% {
57
  background-position: center;
58
+ background-size: 150% ;
59
  }
60
  25% {
61
  background-position: top left;
62
+ background-size: 120% ;
63
  }
64
  50% {
65
  background-position: bottom right;
66
+ background-size: 150% ;
67
  }
68
  75% {
69
  background-position: top right;
70
+ background-size: 120% ;
71
  }
72
  100% {
73
  background-position: center;
74
+ background-size: 150% ;
75
  }
76
  }
77
  /* Container Styling */
frontend/src/app/movie/[title]/page.js CHANGED
@@ -10,8 +10,8 @@ import {
10
  } from "@fortawesome/free-solid-svg-icons";
11
  import { faBookmark as faBookmarkRegular } from "@fortawesome/free-regular-svg-icons";
12
  import apiClient from "@/api/apiClient";
13
- import { Spinner } from "@/components/Spinner";
14
- import CastSection from "@/components/CastSection";
15
  import NotFound from "@/app/not-found";
16
 
17
  export default function MovieDetailsPage({ params }) {
@@ -69,7 +69,7 @@ export default function MovieDetailsPage({ params }) {
69
  const genres = metadata?.data?.genres || [];
70
  const cast = metadata?.data?.characters || [];
71
  return (
72
- <div className="movie-details-page">
73
  <div
74
  className="movie-details-backdrop"
75
  style={{
 
10
  } from "@fortawesome/free-solid-svg-icons";
11
  import { faBookmark as faBookmarkRegular } from "@fortawesome/free-regular-svg-icons";
12
  import apiClient from "@/api/apiClient";
13
+ import { Spinner } from "@/components/shared/Spinner/Spinner";
14
+ import CastSection from "@/components/shared/Sections/CastSection";
15
  import NotFound from "@/app/not-found";
16
 
17
  export default function MovieDetailsPage({ params }) {
 
69
  const genres = metadata?.data?.genres || [];
70
  const cast = metadata?.data?.characters || [];
71
  return (
72
+ <div className="movie-details-page app-container">
73
  <div
74
  className="movie-details-backdrop"
75
  style={{
frontend/src/app/movies/filmsPage.css CHANGED
@@ -6,11 +6,7 @@
6
  padding: 20px;
7
  max-width: 1200px;
8
  margin: 0 auto;
9
- height: 80dvh;
10
- overflow-y: scroll;
11
- -ms-overflow-style: none; /* IE and Edge */
12
- scrollbar-width: none; /* Firefox */
13
- scroll-behavior: smooth;
14
  }
15
 
16
  /* Films Grid */
@@ -19,30 +15,31 @@
19
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
20
  gap: 30px;
21
  width: 100%;
 
22
  justify-content: center;
23
  }
24
 
25
  /* Media Queries for Responsiveness */
26
  @media (max-width: 768px) {
27
  .films-page {
28
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
29
- gap: 15px;
30
  }
31
  }
32
 
33
  @media (max-width: 480px) {
34
  .films-page {
35
- grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
36
  }
37
  }
38
 
39
  /* Pagination Controls */
40
  .pagination-controls {
41
- margin-top: 20px;
42
  display: flex;
43
  align-items: center;
44
  position: absolute;
45
- bottom: 1dvh;
 
46
  }
47
 
48
  /* Pagination Button */
 
6
  padding: 20px;
7
  max-width: 1200px;
8
  margin: 0 auto;
9
+ height: 100dvh;
 
 
 
 
10
  }
11
 
12
  /* Films Grid */
 
15
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
16
  gap: 30px;
17
  width: 100%;
18
+ height: auto;
19
  justify-content: center;
20
  }
21
 
22
  /* Media Queries for Responsiveness */
23
  @media (max-width: 768px) {
24
  .films-page {
25
+ grid-template-columns: repeat(auto-fill, minmax(120px, 10rem));
26
+ gap: 5px;
27
  }
28
  }
29
 
30
  @media (max-width: 480px) {
31
  .films-page {
32
+ grid-template-columns: repeat(auto-fill, minmax(120px, 10rem));
33
  }
34
  }
35
 
36
  /* Pagination Controls */
37
  .pagination-controls {
 
38
  display: flex;
39
  align-items: center;
40
  position: absolute;
41
+ top: 93dvh;
42
+ transition: top .2s ease;
43
  }
44
 
45
  /* Pagination Button */
frontend/src/app/movies/page.js CHANGED
@@ -1,15 +1,18 @@
1
- 'use client';
2
- import { useEffect, useState } from 'react';
3
- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4
- import apiClient from '@/api/apiClient';
5
- import MovieCard from '@/components/MovieCard';
6
- import { useFilmContext } from '@/context/FilmContext';
7
- import './filmsPage.css';
8
 
9
- import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
10
- import Link from 'next/link';
 
 
 
11
 
12
- const FILMS_PER_PAGE = 3;
13
 
14
  export default function FilmsPage() {
15
  const { films, setFilms } = useFilmContext();
@@ -17,12 +20,13 @@ export default function FilmsPage() {
17
 
18
  useEffect(() => {
19
  if (films.length === 0) {
20
- apiClient.getAllMovies()
21
- .then(response => {
22
- setFilms(response.map(film => film.replace('films/', '')));
 
23
  })
24
- .catch(error => {
25
- console.error('Failed to get all films:', error);
26
  });
27
  }
28
  }, [films, setFilms]);
@@ -31,11 +35,11 @@ export default function FilmsPage() {
31
  const currentFilms = films.slice(startIndex, startIndex + FILMS_PER_PAGE);
32
 
33
  const handleNextPage = () => {
34
- setCurrentPage(prevPage => prevPage + 1);
35
  };
36
 
37
  const handlePrevPage = () => {
38
- setCurrentPage(prevPage => Math.max(prevPage - 1, 1));
39
  };
40
 
41
  const totalPages = Math.ceil(films.length / FILMS_PER_PAGE);
@@ -43,37 +47,33 @@ export default function FilmsPage() {
43
  const isNextButtonEnabled = currentPage < totalPages;
44
 
45
  return (
46
- <div className="films-page-container">
47
  <div className="films-page">
48
  {currentFilms.map((title, index) => (
49
- <Link key={index} href={`/movie/${title}`}>
50
- <MovieCard title={title} />
51
- </Link>
52
  ))}
53
  </div>
54
  <div className="pagination-controls">
55
- <button
56
- onClick={handlePrevPage}
57
  disabled={!isPrevButtonEnabled}
58
- className={`pagination-button ${isPrevButtonEnabled ? 'enabled' : 'disabled'}`}
 
 
59
  >
60
- <FontAwesomeIcon
61
- icon={faChevronLeft}
62
- size="xl"
63
- />
64
  </button>
65
  <span className="page-info">
66
  {currentPage} of {totalPages}
67
  </span>
68
- <button
69
- onClick={handleNextPage}
70
  disabled={!isNextButtonEnabled}
71
- className={`pagination-button ${isNextButtonEnabled ? 'enabled' : 'disabled'}`}
 
 
72
  >
73
- <FontAwesomeIcon
74
- icon={faChevronRight}
75
- size="xl"
76
- />
77
  </button>
78
  </div>
79
  </div>
 
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4
+ import apiClient from "@/api/apiClient";
5
+ import MovieCard from "@/components/shared/Card/MovieCard";
6
+ import { useFilmContext } from "@/context/FilmContext";
7
+ import "./filmsPage.css";
8
 
9
+ import {
10
+ faChevronLeft,
11
+ faChevronRight,
12
+ } from "@fortawesome/free-solid-svg-icons";
13
+ import Link from "next/link";
14
 
15
+ const FILMS_PER_PAGE = 10;
16
 
17
  export default function FilmsPage() {
18
  const { films, setFilms } = useFilmContext();
 
20
 
21
  useEffect(() => {
22
  if (films.length === 0) {
23
+ apiClient
24
+ .getAllMovies()
25
+ .then((response) => {
26
+ setFilms(response.map((film) => film.replace("films/", "")));
27
  })
28
+ .catch((error) => {
29
+ console.error("Failed to get all films:", error);
30
  });
31
  }
32
  }, [films, setFilms]);
 
35
  const currentFilms = films.slice(startIndex, startIndex + FILMS_PER_PAGE);
36
 
37
  const handleNextPage = () => {
38
+ setCurrentPage((prevPage) => prevPage + 1);
39
  };
40
 
41
  const handlePrevPage = () => {
42
+ setCurrentPage((prevPage) => Math.max(prevPage - 1, 1));
43
  };
44
 
45
  const totalPages = Math.ceil(films.length / FILMS_PER_PAGE);
 
47
  const isNextButtonEnabled = currentPage < totalPages;
48
 
49
  return (
50
+ <div className="films-page-container app-container">
51
  <div className="films-page">
52
  {currentFilms.map((title, index) => (
53
+ <MovieCard key={index} title={title} />
 
 
54
  ))}
55
  </div>
56
  <div className="pagination-controls">
57
+ <button
58
+ onClick={handlePrevPage}
59
  disabled={!isPrevButtonEnabled}
60
+ className={`pagination-button ${
61
+ isPrevButtonEnabled ? "enabled" : "disabled"
62
+ }`}
63
  >
64
+ <FontAwesomeIcon icon={faChevronLeft} size="xl" />
 
 
 
65
  </button>
66
  <span className="page-info">
67
  {currentPage} of {totalPages}
68
  </span>
69
+ <button
70
+ onClick={handleNextPage}
71
  disabled={!isNextButtonEnabled}
72
+ className={`pagination-button ${
73
+ isNextButtonEnabled ? "enabled" : "disabled"
74
+ }`}
75
  >
76
+ <FontAwesomeIcon icon={faChevronRight} size="xl" />
 
 
 
77
  </button>
78
  </div>
79
  </div>
frontend/src/app/not-found.js CHANGED
@@ -4,12 +4,13 @@ export default function NotFound() {
4
  return (
5
  <div style={styles.container}>
6
  <div style={styles.overlay}></div>
7
- <h2 style={styles.title}>Whoops!</h2>
8
  <p style={styles.message}>
9
- Sorry, the page you&apos;re looking for doesn&apos;t exist.
 
10
  </p>
11
  <Link href="/" style={styles.link}>
12
- Go Home
13
  </Link>
14
  </div>
15
  );
@@ -36,24 +37,24 @@ const styles = {
36
  left: 0,
37
  right: 0,
38
  bottom: 0,
39
- background: 'url("/404_bg2.jpg") no-repeat center center/cover',
40
  filter: "blur(5px)",
41
  zIndex: -1, // Sends the overlay behind the text
42
  },
43
  title: {
44
- color:'var(--primary-special-color)',
45
  fontSize: "3rem",
46
  fontWeight: "bold",
47
  textShadow: "2px 2px 4px rgba(0, 0, 0, 0.7)",
 
48
  },
49
  message: {
50
  fontSize: "1.5rem",
51
- fontWeight: "200",
52
- margin: "20px 0",
53
  textShadow: "1px 1px 3px rgba(0, 0, 0, 0.7)",
54
  },
55
  link: {
56
- marginTop: "20px",
57
  padding: "12px 20px",
58
  backgroundColor: "var(--bg-primary)", // Semi-transparent red
59
  color: "white",
 
4
  return (
5
  <div style={styles.container}>
6
  <div style={styles.overlay}></div>
7
+ <h2 style={styles.title}>Whoops! 🚧</h2>
8
  <p style={styles.message}>
9
+ It looks like this page wandered off into the internet abyss. <br />
10
+ Let's get you back on track!
11
  </p>
12
  <Link href="/" style={styles.link}>
13
+ Go 🏚️Home
14
  </Link>
15
  </div>
16
  );
 
37
  left: 0,
38
  right: 0,
39
  bottom: 0,
40
+ background: 'url("/deep-sea-abyss.jpg") no-repeat center center/cover',
41
  filter: "blur(5px)",
42
  zIndex: -1, // Sends the overlay behind the text
43
  },
44
  title: {
45
+ color: 'var(--primary-special-color)',
46
  fontSize: "3rem",
47
  fontWeight: "bold",
48
  textShadow: "2px 2px 4px rgba(0, 0, 0, 0.7)",
49
+ marginBottom: "20px",
50
  },
51
  message: {
52
  fontSize: "1.5rem",
53
+ fontWeight: "300",
54
+ marginBottom: "20px",
55
  textShadow: "1px 1px 3px rgba(0, 0, 0, 0.7)",
56
  },
57
  link: {
 
58
  padding: "12px 20px",
59
  backgroundColor: "var(--bg-primary)", // Semi-transparent red
60
  color: "white",
frontend/src/app/page.js CHANGED
@@ -1,5 +1,6 @@
1
- import HeroSection from "@/components/HeroSection";
2
  import "./index.css";
 
3
 
4
  export const metadata = {
5
  title: "Home",
@@ -10,15 +11,17 @@ export const metadata = {
10
 
11
  const HomePage = () => {
12
  return (
13
- <>
14
  <div className="index-page">
15
  <div className="index-page-container">
16
  <HeroSection />
17
- <h2 className="section-title">Popular TV Shows</h2>
18
- <h2 className="section-title">Trending Movies</h2>
 
 
19
  </div>
20
  </div>
21
- </>
22
  );
23
  };
24
 
 
1
+ import HeroSection from "@/components/shared/Sections/HeroSection";
2
  import "./index.css";
3
+ import ScrollSection from "@/components/shared/Sections/ScrollSection";
4
 
5
  export const metadata = {
6
  title: "Home",
 
11
 
12
  const HomePage = () => {
13
  return (
14
+ <div className="app-container">
15
  <div className="index-page">
16
  <div className="index-page-container">
17
  <HeroSection />
18
+ <div className="index-page-content">
19
+ <ScrollSection title="Popular TV Shows"></ScrollSection>
20
+ <ScrollSection title="Trending Movies"></ScrollSection>
21
+ </div>
22
  </div>
23
  </div>
24
+ </div>
25
  );
26
  };
27
 
frontend/src/app/player/movie/[title]/MoviePlayerPage.css CHANGED
@@ -25,3 +25,13 @@
25
  align-items: center;
26
  height: 100%;
27
  }
 
 
 
 
 
 
 
 
 
 
 
25
  align-items: center;
26
  height: 100%;
27
  }
28
+
29
+ .subtitle-inputs {
30
+ width: 30%;
31
+ height: 10%;
32
+ background-color: green;
33
+ position: absolute;
34
+ top: 0;
35
+ right: 0;
36
+ z-index: 1000;
37
+ }
frontend/src/app/player/movie/[title]/page.js CHANGED
@@ -3,7 +3,7 @@ import apiClient from "@/api/apiClient";
3
  import { useEffect, useState } from "react";
4
  import MoviePlayer from "@/components/MoviePlayer";
5
  import "./MoviePlayerPage.css";
6
- import { Spinner } from "@/components/Spinner";
7
  import { ProgressBar, Container, Row, Col, Alert } from "react-bootstrap";
8
  import "bootstrap/dist/css/bootstrap.min.css";
9
  import NotFound from "@/app/not-found";
 
3
  import { useEffect, useState } from "react";
4
  import MoviePlayer from "@/components/MoviePlayer";
5
  import "./MoviePlayerPage.css";
6
+ import { Spinner } from "@/components/shared/Spinner/Spinner";
7
  import { ProgressBar, Container, Row, Col, Alert } from "react-bootstrap";
8
  import "bootstrap/dist/css/bootstrap.min.css";
9
  import NotFound from "@/app/not-found";
frontend/src/app/search/page.js CHANGED
@@ -9,8 +9,9 @@ import {
9
  } from "@fortawesome/free-solid-svg-icons";
10
  import { search } from "@/api/searchApi";
11
  import "./searchPage.css";
12
- import MovieCard from "@/components/MovieCard";
13
- import TvShowCard from "@/components/TvShowCard";
 
14
 
15
  const SearchPage = () => {
16
  const [query, setQuery] = useState("");
@@ -22,8 +23,6 @@ const SearchPage = () => {
22
 
23
  const router = useRouter();
24
  const timeoutRef = useRef(null);
25
- const moviesRef = useRef(null);
26
- const seriesRef = useRef(null);
27
 
28
  useEffect(() => {
29
  if (timeoutRef.current) {
@@ -77,7 +76,7 @@ const SearchPage = () => {
77
  };
78
 
79
  return (
80
- <div className="search-page">
81
  <div className="search-container">
82
  <input
83
  type="text"
@@ -87,7 +86,7 @@ const SearchPage = () => {
87
  className="search-input"
88
  />
89
  {loading && (
90
- <div className="loading-indicator">
91
  <div className="search-icon">
92
  <FontAwesomeIcon icon={faSearch} size="xl" />
93
  </div>
@@ -98,72 +97,18 @@ const SearchPage = () => {
98
  {error && <div className="error-message">{error}</div>}
99
  <div className="results-container">
100
  {results.films.length > 0 && (
101
- <div className="results-section">
102
- <div className="result-scroll">
103
- <h2>Films</h2>
104
- <div className="scroll-controls">
105
- <button
106
- onClick={() => scroll(moviesRef, "left")}
107
- className="scroll-button"
108
- >
109
- <FontAwesomeIcon icon={faCaretLeft} size="2xl" />
110
- </button>
111
- <button
112
- onClick={() => scroll(moviesRef, "right")}
113
- className="scroll-button"
114
- >
115
- <FontAwesomeIcon icon={faCaretRight} size="2xl" />
116
- </button>
117
- </div>
118
- </div>
119
- <ul className="results-list" ref={moviesRef}>
120
- {results.films.map((film, index) => (
121
- <li
122
- key={index}
123
- className="result-item"
124
- onClick={() =>
125
- handleItemClick(`/movie/${encodeURIComponent(film)}`)
126
- }
127
- >
128
- <MovieCard title={film} />
129
- </li>
130
- ))}
131
- </ul>
132
- </div>
133
  )}
134
  {results.tv_series.length > 0 && (
135
- <div className="results-section">
136
- <div className="result-scroll">
137
- <h2>TV Series</h2>
138
- <div className="scroll-controls">
139
- <button
140
- onClick={() => scroll(seriesRef, "left")}
141
- className="scroll-button"
142
- >
143
- <FontAwesomeIcon icon={faCaretLeft} size="2xl" />
144
- </button>
145
- <button
146
- onClick={() => scroll(seriesRef, "right")}
147
- className="scroll-button"
148
- >
149
- <FontAwesomeIcon icon={faCaretRight} size="2xl" />
150
- </button>
151
- </div>
152
- </div>
153
- <ul className="results-list" ref={seriesRef}>
154
- {results.tv_series.map((series, index) => (
155
- <li
156
- key={index}
157
- className="result-item"
158
- onClick={() =>
159
- handleItemClick(`/tvshow/${encodeURIComponent(series)}`)
160
- }
161
- >
162
- <TvShowCard title={series} />
163
- </li>
164
- ))}
165
- </ul>
166
- </div>
167
  )}
168
  </div>
169
  </div>
 
9
  } from "@fortawesome/free-solid-svg-icons";
10
  import { search } from "@/api/searchApi";
11
  import "./searchPage.css";
12
+ import MovieCard from "@/components/shared/Card/MovieCard";
13
+ import TvShowCard from "@/components/shared/Card/TvShowCard";
14
+ import ScrollSection from "@/components/shared/Sections/ScrollSection";
15
 
16
  const SearchPage = () => {
17
  const [query, setQuery] = useState("");
 
23
 
24
  const router = useRouter();
25
  const timeoutRef = useRef(null);
 
 
26
 
27
  useEffect(() => {
28
  if (timeoutRef.current) {
 
76
  };
77
 
78
  return (
79
+ <div className="search-page app-container">
80
  <div className="search-container">
81
  <input
82
  type="text"
 
86
  className="search-input"
87
  />
88
  {loading && (
89
+ <div className="search-indicator">
90
  <div className="search-icon">
91
  <FontAwesomeIcon icon={faSearch} size="xl" />
92
  </div>
 
97
  {error && <div className="error-message">{error}</div>}
98
  <div className="results-container">
99
  {results.films.length > 0 && (
100
+ <ScrollSection title={"Movies"}>
101
+ {results.films.map((film, index) => (
102
+ <MovieCard key={index} title={film} />
103
+ ))}
104
+ </ScrollSection>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  )}
106
  {results.tv_series.length > 0 && (
107
+ <ScrollSection title={"TV Series"}>
108
+ {results.tv_series.map((series, index) => (
109
+ <TvShowCard key={index} title={series} />
110
+ ))}
111
+ </ScrollSection>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  )}
113
  </div>
114
  </div>
frontend/src/app/search/searchPage.css CHANGED
@@ -1,9 +1,7 @@
1
  .search-page {
2
- background-color: #121212;
3
  color: #ffffff;
4
  height: 100%;
5
  min-height: 89dvh;
6
- transition: background-color 0.3s ease;
7
  font-family: "Signika", sans-serif;
8
  font-optical-sizing: auto;
9
  font-style: normal;
@@ -83,17 +81,11 @@
83
  .results-container {
84
  padding-left: 20px;
85
  padding-right: 20px;
86
- display: flex;
87
- flex-direction: column;
88
- align-items: center;
89
  }
90
 
91
  .results-section {
92
  width: 100%;
93
  max-width: 100%;
94
- display: flex;
95
- flex-direction: column;
96
- white-space: nowrap;
97
  }
98
 
99
  .results-section h2 {
@@ -102,50 +94,6 @@
102
  animation: fadeIn 1s ease;
103
  }
104
 
105
- .result-scroll {
106
- display: flex;
107
- justify-content: space-between;
108
- margin-bottom: 15px;
109
- border-bottom: 1px solid #4a41ef;
110
- padding-bottom: 5px;
111
- }
112
- .scroll-controls button {
113
- margin-left: 10px;
114
- margin-right: 10px;
115
- color: #9b9a9f;
116
- }
117
-
118
- .results-list {
119
- list-style-type: none;
120
- padding: 0;
121
- display: flex;
122
- flex-direction: row;
123
- margin: 0;
124
- scroll-behavior: smooth;
125
- overflow-x: scroll;
126
- overflow-y: hidden;
127
- }
128
-
129
- .results-list::-webkit-scrollbar {
130
- height: 8px;
131
- }
132
-
133
- .results-list::-webkit-scrollbar-thumb {
134
- background-color: #3c23cf;
135
- border-radius: 4px;
136
- }
137
-
138
- .result-item {
139
- background-color: #1e1e1e;
140
- border: 1px solid #585858;
141
- border-radius: 5px;
142
- margin-right: 10px;
143
- transition: background-color 0.3s ease, transform 0.3s ease;
144
- animation: slideIn 0.5s ease;
145
- display: inline-block;
146
- flex: 0 0 auto;
147
- }
148
-
149
  @keyframes slideIn {
150
  from {
151
  transform: translateY(20px);
@@ -156,27 +104,8 @@
156
  opacity: 1;
157
  }
158
  }
159
-
160
- .result-item:hover {
161
- background-color: #333;
162
- transform: scale(1.02);
163
- }
164
-
165
- /* Link styling */
166
- .result-link {
167
- text-decoration: none;
168
- color: #ffffff;
169
- font-size: 18px;
170
- transition: color 0.3s ease;
171
- }
172
-
173
- .result-link:hover {
174
- text-decoration: underline;
175
- color: #ff6f00;
176
- }
177
-
178
  /* Loading indicator styles */
179
- .loading-indicator {
180
  display: flex;
181
  align-items: center;
182
  margin-top: 20px;
@@ -257,7 +186,7 @@
257
  }
258
  }
259
 
260
- .loading-indicator .spinner {
261
  width: 40px;
262
  height: 40px;
263
  background-image: linear-gradient(#146c98, #7139ff);
 
1
  .search-page {
 
2
  color: #ffffff;
3
  height: 100%;
4
  min-height: 89dvh;
 
5
  font-family: "Signika", sans-serif;
6
  font-optical-sizing: auto;
7
  font-style: normal;
 
81
  .results-container {
82
  padding-left: 20px;
83
  padding-right: 20px;
 
 
 
84
  }
85
 
86
  .results-section {
87
  width: 100%;
88
  max-width: 100%;
 
 
 
89
  }
90
 
91
  .results-section h2 {
 
94
  animation: fadeIn 1s ease;
95
  }
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  @keyframes slideIn {
98
  from {
99
  transform: translateY(20px);
 
104
  opacity: 1;
105
  }
106
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  /* Loading indicator styles */
108
+ .search-indicator {
109
  display: flex;
110
  align-items: center;
111
  margin-top: 20px;
 
186
  }
187
  }
188
 
189
+ .search-indicator .spinner {
190
  width: 40px;
191
  height: 40px;
192
  background-image: linear-gradient(#146c98, #7139ff);
frontend/src/app/tvshows/page.js CHANGED
@@ -1,8 +1,8 @@
1
- import NotFound from '../not-found';
2
  import './TvShows.css';
3
 
4
  export default function TVShows() {
5
  return (
6
- <NotFound/>
7
  );
8
  }
 
1
+ import StillInDevelopment from '../_still-in-development';
2
  import './TvShows.css';
3
 
4
  export default function TVShows() {
5
  return (
6
+ <StillInDevelopment/>
7
  );
8
  }
frontend/src/components/MovieCard.css DELETED
@@ -1,83 +0,0 @@
1
- /* styles/MovieCard.css */
2
-
3
- .movie-card {
4
- position: relative;
5
- width: 150px;
6
- height: 250px;
7
- margin: 10px;
8
- border-radius: 8px;
9
- overflow: hidden;
10
- background-color: #202232;
11
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
12
- cursor: pointer;
13
- transition: transform 0.3s ease;
14
- display: flex;
15
- flex-direction: column;
16
- opacity: 0;
17
- animation: fadeIn 0.5s forwards;
18
- }
19
-
20
- .movie-card-image-container {
21
- position: relative;
22
- width: 100%;
23
- height: 80%;
24
- }
25
-
26
- .movie-card-poster {
27
- object-fit: cover;
28
- object-position: top;
29
- border-radius: 8px 8px 0 0;
30
- animation: fade-in 2s forwards;
31
- opacity: 0;
32
- }
33
-
34
- @keyframes fade-in{
35
- 0%{opacity: 0;}
36
- 100%{opacity: 1;}
37
- }
38
-
39
- .movie-card-info {
40
- position: relative;
41
- width: 100%;
42
- height: 28%;
43
- padding: 10px;
44
- background: #1b1c24;
45
- color: #fff;
46
- text-align: center;
47
- box-sizing: border-box;
48
- display: flex;
49
- flex-direction: column;
50
- justify-content: center;
51
- text-decoration: none !important;
52
- }
53
-
54
- .movie-card-title {
55
- margin: 0;
56
- font-size: 16px;
57
- font-weight: bold;
58
- overflow: hidden;
59
- text-overflow: ellipsis;
60
- white-space: nowrap;
61
- }
62
-
63
- .movie-card-year {
64
- margin: 5px 0 0;
65
- font-size: 14px;
66
- overflow: hidden;
67
- text-overflow: ellipsis;
68
- white-space: nowrap;
69
- }
70
-
71
- .error {
72
- color: #e74c3c;
73
- }
74
-
75
- @keyframes fadeIn {
76
- from {
77
- opacity: 0;
78
- }
79
- to {
80
- opacity: 1;
81
- }
82
- }
83
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/{MoviePlayer.css β†’ Player/Movie/MoviePlayer.css} RENAMED
@@ -229,3 +229,10 @@ input[type="range"]::-moz-range-thumb {
229
  width: 0.8rem;
230
  border-radius: 10px;
231
  }
 
 
 
 
 
 
 
 
229
  width: 0.8rem;
230
  border-radius: 10px;
231
  }
232
+
233
+ ::cue {
234
+ color: rgb(199, 199, 199);
235
+ background-color: rgba(0, 0, 0, 0.7);
236
+ font-size: 1.3em;
237
+ line-height: 1.2; /* Adjust line height for spacing */
238
+ }
frontend/src/components/{MoviePlayer.js β†’ Player/Movie/MoviePlayer.js} RENAMED
@@ -15,7 +15,7 @@ import {
15
  faAngleDoubleLeft,
16
  faCompress,
17
  } from "@fortawesome/free-solid-svg-icons";
18
- import { Spinner } from "@/components/Spinner";
19
  import SeekableProgressBar from "./SeekableProgressBar";
20
 
21
  export default function MoviePlayer({ videoUrl, title }) {
@@ -277,7 +277,8 @@ export default function MoviePlayer({ videoUrl, title }) {
277
  kind="subtitles"
278
  label="English"
279
  srcLang="en"
280
- src="path/to/your/subtitles.vtt" // Replace with actual subtitles URL
 
281
  />
282
  Your browser does not support the video tag.
283
  </video>
 
15
  faAngleDoubleLeft,
16
  faCompress,
17
  } from "@fortawesome/free-solid-svg-icons";
18
+ import { Spinner } from "@/components/shared/Spinner/Spinner";
19
  import SeekableProgressBar from "./SeekableProgressBar";
20
 
21
  export default function MoviePlayer({ videoUrl, title }) {
 
277
  kind="subtitles"
278
  label="English"
279
  srcLang="en"
280
+ src="/My.Spy.The.Eternal.City.(2024).WEB.vtt"
281
+ default
282
  />
283
  Your browser does not support the video tag.
284
  </video>
frontend/src/components/shared/Card/Card.css ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .movie-card:hover .movie-card-poster{
2
+ transform: scale(1.1);
3
+ }
4
+
5
+ .movie-card {
6
+ position: relative;
7
+ width: 150px;
8
+ height: 250px;
9
+ min-width: 150px;
10
+ min-height: 250px;
11
+ margin: 10px;
12
+ border-radius: 8px;
13
+ overflow: hidden;
14
+ background-color: #202232;
15
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
16
+ cursor: pointer;
17
+ transition: transform 0.3s ease;
18
+ display: flex;
19
+ flex-direction: column;
20
+ opacity: 0;
21
+ animation: fadeIn 0.5s forwards;
22
+ }
23
+
24
+ .movie-card-image-container {
25
+ position: relative;
26
+ width: 100%;
27
+ height: inherit;
28
+ }
29
+
30
+ .movie-card-poster {
31
+ object-fit: cover;
32
+ object-position: top;
33
+ border-radius: 8px 8px 0 0;
34
+ animation: fade-in 2s forwards;
35
+ opacity: 0;
36
+ transition: transform .1s ease;
37
+ }
38
+
39
+ @keyframes fade-in {
40
+ 0% {
41
+ opacity: 0;
42
+ }
43
+ 100% {
44
+ opacity: 1;
45
+ }
46
+ }
47
+
48
+ .movie-card-info {
49
+ position: relative;
50
+ width: 100%;
51
+ height: 28%;
52
+ padding: 10px;
53
+ background: #1b1c24;
54
+ color: #fff;
55
+ text-align: center;
56
+ box-sizing: border-box;
57
+ display: flex;
58
+ flex-direction: column;
59
+ justify-content: center;
60
+ text-decoration: none !important;
61
+ }
62
+
63
+ .movie-card-title {
64
+ margin: 0;
65
+ font-size: 16px;
66
+ font-weight: 500;
67
+ overflow: hidden;
68
+ text-overflow: ellipsis;
69
+ white-space: nowrap;
70
+ }
71
+
72
+ .movie-card-year {
73
+ margin: 5px 0 0;
74
+ font-size: 1rem;
75
+ font-weight: 200;
76
+ overflow: hidden;
77
+ text-overflow: ellipsis;
78
+ white-space: nowrap;
79
+ }
80
+
81
+ .error {
82
+ color: #e74c3c;
83
+ }
84
+
85
+ @keyframes fadeIn {
86
+ from {
87
+ opacity: 0;
88
+ }
89
+ to {
90
+ opacity: 1;
91
+ }
92
+ }
93
+
94
+ @media (orientation: portrait) {
95
+ .movie-card {
96
+ width: 130px;
97
+ height: 220px;
98
+ margin: 5px;
99
+ }
100
+ .movie-card-info {
101
+ height: 30%;
102
+ padding: 5px;
103
+ }
104
+ .movie-card-title {
105
+ margin: 0;
106
+ font-size: 1rem;
107
+ }
108
+
109
+ .movie-card-year {
110
+ margin: 5px 0 0;
111
+ font-size: 0.8rem;
112
+ }
113
+ }
frontend/src/components/{MovieCard.js β†’ shared/Card/MovieCard.js} RENAMED
@@ -1,8 +1,9 @@
1
- import { useEffect, useState } from 'react';
2
- import Image from 'next/image';
3
- import apiClient from '@/api/apiClient';
4
- import SkeletonLoader from '@/skeletons/movieCard';
5
- import './MovieCard.css';
 
6
 
7
  const MovieCard = ({ title }) => {
8
  const [movieData, setMovieData] = useState(null);
@@ -33,22 +34,24 @@ const MovieCard = ({ title }) => {
33
  }
34
 
35
  return (
36
- <div className="movie-card">
37
- <div className="movie-card-image-container">
38
- <Image
39
- src={movieData.image}
40
- alt={`${movieData.title} poster`}
41
- fill
42
- sizes="(max-width: 500px) 100vw, (max-width: 1200spx) 50vw, 33vw"
43
- priority
44
- className="movie-card-poster"
45
- />
 
 
 
 
 
 
46
  </div>
47
- <div className="movie-card-info">
48
- <h3 className="movie-card-title">{movieData.title}</h3>
49
- <p className="movie-card-year">{movieData.year}</p>
50
- </div>
51
- </div>
52
  );
53
  };
54
 
 
1
+ import { useEffect, useState } from "react";
2
+ import Image from "next/image";
3
+ import apiClient from "@/api/apiClient";
4
+ import SkeletonLoader from "@/skeletons/Card/movieCard";
5
+ import "./Card.css";
6
+ import Link from "next/link";
7
 
8
  const MovieCard = ({ title }) => {
9
  const [movieData, setMovieData] = useState(null);
 
34
  }
35
 
36
  return (
37
+ <Link href={`/movie/${title}`}>
38
+ <div className="movie-card">
39
+ <div className="movie-card-image-container">
40
+ <Image
41
+ src={movieData.image}
42
+ alt={`${movieData.title} poster`}
43
+ fill
44
+ sizes="(max-width: 500px) 100vw, (max-width: 1200spx) 50vw, 33vw"
45
+ priority
46
+ className="movie-card-poster"
47
+ />
48
+ </div>
49
+ <div className="movie-card-info">
50
+ <h3 className="movie-card-title">{movieData.title}</h3>
51
+ <p className="movie-card-year">{movieData.year}</p>
52
+ </div>
53
  </div>
54
+ </Link>
 
 
 
 
55
  );
56
  };
57
 
frontend/src/components/{TvShowCard.js β†’ shared/Card/TvShowCard.js} RENAMED
@@ -1,8 +1,8 @@
1
  import { useEffect, useState } from 'react';
2
  import Image from 'next/image';
3
  import apiClient from '@/api/apiClient';
4
- import SkeletonLoader from '@/skeletons/movieCard';
5
- import './MovieCard.css';
6
 
7
  const TvShowCard = ({ title }) => {
8
  const [movieData, setMovieData] = useState(null);
 
1
  import { useEffect, useState } from 'react';
2
  import Image from 'next/image';
3
  import apiClient from '@/api/apiClient';
4
+ import SkeletonLoader from '@/skeletons/Card/movieCard';
5
+ import './Card.css';
6
 
7
  const TvShowCard = ({ title }) => {
8
  const [movieData, setMovieData] = useState(null);
frontend/src/components/{Header.css β†’ shared/Header/Header.css} RENAMED
File without changes
frontend/src/components/{Header.js β†’ shared/Header/Header.js} RENAMED
@@ -6,7 +6,7 @@ import { faSearch } from "@fortawesome/free-solid-svg-icons";
6
  import Image from "next/image";
7
  import { useEffect } from "react";
8
  import { usePathname } from "next/navigation";
9
- import Sidebar from "@/components/Sidebar";
10
 
11
  const Header = () => {
12
  const pathname = usePathname();
@@ -14,17 +14,6 @@ const Header = () => {
14
  pathname.startsWith("/player/movie") ||
15
  pathname.startsWith("/player/tvshow");
16
 
17
- useEffect(() => {
18
- const appContainer = document.querySelector('.app-container');
19
- if (appContainer) {
20
- if (isPlayerPage) {
21
- appContainer.classList.add('no-padding');
22
- } else {
23
- appContainer.classList.remove('no-padding');
24
- }
25
- }
26
- }, [isPlayerPage]);
27
-
28
  return (
29
  <>
30
  {!isPlayerPage && (
 
6
  import Image from "next/image";
7
  import { useEffect } from "react";
8
  import { usePathname } from "next/navigation";
9
+ import Sidebar from "@/components/shared/Sidebar/Sidebar";
10
 
11
  const Header = () => {
12
  const pathname = usePathname();
 
14
  pathname.startsWith("/player/movie") ||
15
  pathname.startsWith("/player/tvshow");
16
 
 
 
 
 
 
 
 
 
 
 
 
17
  return (
18
  <>
19
  {!isPlayerPage && (
frontend/src/components/{SeekableProgressBar.css β†’ shared/ProgressBar/SeekableProgressBar.css} RENAMED
File without changes
frontend/src/components/{SeekableProgressBar.js β†’ shared/ProgressBar/SeekableProgressBar.js} RENAMED
File without changes
frontend/src/components/{CastSection.css β†’ shared/Sections/CastSection.css} RENAMED
File without changes
frontend/src/components/{CastSection.js β†’ shared/Sections/CastSection.js} RENAMED
File without changes
frontend/src/components/{HeroSection.css β†’ shared/Sections/HeroSection.css} RENAMED
@@ -44,23 +44,23 @@
44
  @keyframes hero-anim-portrait {
45
  0% {
46
  background-position: center;
47
- background-size: 160% 140%;
48
  }
49
  25% {
50
  background-position: top left;
51
- background-size: 120% 100%;
52
  }
53
  50% {
54
  background-position: bottom right;
55
- background-size: 160% 140%;
56
  }
57
  75% {
58
  background-position: top right;
59
- background-size: 120% 100%;
60
  }
61
  100% {
62
  background-position: center;
63
- background-size: 160% 140%;
64
  }
65
  }
66
 
@@ -68,23 +68,23 @@
68
  @keyframes hero-anim-landscape {
69
  0% {
70
  background-position: center;
71
- background-size: 130% 150%;
72
  }
73
  25% {
74
  background-position: top left;
75
- background-size: 100% 120%;
76
  }
77
  50% {
78
  background-position: bottom right;
79
- background-size: 130% 150%;
80
  }
81
  75% {
82
  background-position: top right;
83
- background-size: 100% 120%;
84
  }
85
  100% {
86
  background-position: center;
87
- background-size: 130% 150%;
88
  }
89
  }
90
 
@@ -106,6 +106,10 @@
106
  justify-content: space-around;
107
  }
108
 
 
 
 
 
109
  .hero-title {
110
  color: white;
111
  font-size: 7rem;
 
44
  @keyframes hero-anim-portrait {
45
  0% {
46
  background-position: center;
47
+ background-size: 160% ;
48
  }
49
  25% {
50
  background-position: top left;
51
+ background-size: 120% ;
52
  }
53
  50% {
54
  background-position: bottom right;
55
+ background-size: 160% ;
56
  }
57
  75% {
58
  background-position: top right;
59
+ background-size: 120% ;
60
  }
61
  100% {
62
  background-position: center;
63
+ background-size: 160% ;
64
  }
65
  }
66
 
 
68
  @keyframes hero-anim-landscape {
69
  0% {
70
  background-position: center;
71
+ background-size: 130% ;
72
  }
73
  25% {
74
  background-position: top left;
75
+ background-size: 100% ;
76
  }
77
  50% {
78
  background-position: bottom right;
79
+ background-size: 130% ;
80
  }
81
  75% {
82
  background-position: top right;
83
+ background-size: 100% ;
84
  }
85
  100% {
86
  background-position: center;
87
+ background-size: 130% ;
88
  }
89
  }
90
 
 
106
  justify-content: space-around;
107
  }
108
 
109
+ .hero-text a{
110
+ text-decoration: none;
111
+ }
112
+
113
  .hero-title {
114
  color: white;
115
  font-size: 7rem;
frontend/src/components/{HeroSection.js β†’ shared/Sections/HeroSection.js} RENAMED
@@ -2,7 +2,7 @@
2
  import { useState, useEffect, useRef } from "react";
3
  import "./HeroSection.css";
4
  import apiClient from "@/api/apiClient";
5
- import SkeletonLoader from "@/skeletons/HeroSection";
6
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7
  import { faPlay } from "@fortawesome/free-solid-svg-icons";
8
  import Link from "next/link";
@@ -121,6 +121,10 @@ const HeroSection = () => {
121
  return <SkeletonLoader />;
122
  }
123
 
 
 
 
 
124
  const { title, description, imageUrl, type } = items[currentIndex];
125
  const linkPath = `/${type.toLowerCase()}/${encodeURIComponent(title)}`;
126
 
 
2
  import { useState, useEffect, useRef } from "react";
3
  import "./HeroSection.css";
4
  import apiClient from "@/api/apiClient";
5
+ import SkeletonLoader from "@/skeletons/Sections/HeroSection";
6
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7
  import { faPlay } from "@fortawesome/free-solid-svg-icons";
8
  import Link from "next/link";
 
121
  return <SkeletonLoader />;
122
  }
123
 
124
+ if (items.length === 0) {
125
+ return <div>No items available</div>;
126
+ }
127
+
128
  const { title, description, imageUrl, type } = items[currentIndex];
129
  const linkPath = `/${type.toLowerCase()}/${encodeURIComponent(title)}`;
130
 
frontend/src/components/shared/Sections/ScrollSection.css ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Section styling */
2
+ .scroll-section {
3
+ margin-bottom: 40px;
4
+ }
5
+
6
+ @media (orientation: landscape) {
7
+ .scroll-section {
8
+ margin-left: 25px;
9
+ margin-right: 25px;
10
+ }
11
+ }
12
+
13
+ @media (max-width: 768px) {
14
+ .scroll-section {
15
+ margin-left: 10px;
16
+ margin-right: 10px;
17
+ margin-bottom: 20px;
18
+ }
19
+ }
20
+
21
+ .scroll-title-section {
22
+ display: flex;
23
+ justify-content: center;
24
+ align-items: center;
25
+ }
26
+
27
+ .scroll-section-controls {
28
+ display: flex;
29
+ align-items: center;
30
+ text-align: center;
31
+ justify-content: space-between;
32
+ flex-wrap: wrap;
33
+ }
34
+
35
+ .scroll-controls button {
36
+ color: var(--gray-text-color);
37
+ margin-left: 10px;
38
+ margin-right: 10px;
39
+ border-radius: 50%;
40
+ width: auto;
41
+ height: auto;
42
+ min-width: 50px;
43
+ padding: 9px;
44
+ border: 1px solid;
45
+ transition: background-color 0.3s ease, border 0.5s ease, color 1s ease;
46
+ }
47
+
48
+ .scroll-controls button:hover {
49
+ background-color: var(--bg-secondary);
50
+ border: 1px solid var(--primary-special-color);
51
+ }
52
+
53
+ .scroll-controls button:disabled {
54
+ color: #797979;
55
+ }
56
+
57
+ .scroll-section h2 {
58
+ font-size: 1.8rem;
59
+ color: var(--gray-text-color);
60
+ }
61
+
62
+ @media (max-width: 768px) {
63
+ .scroll-section h2 {
64
+ font-size: 1.5rem;
65
+ }
66
+ }
67
+
68
+ .items-grid {
69
+ display: flex;
70
+ overflow-x: auto;
71
+ padding: 10px 0;
72
+ gap: 10px;
73
+ flex-direction: row;
74
+ }
75
+
76
+ .items-grid::-webkit-scrollbar {
77
+ display: none;
78
+ }
79
+
80
+ .items-grid::-webkit-scrollbar-thumb {
81
+ background-color: var(--primary-special-color);
82
+ border-radius: 4px;
83
+ }
84
+
85
+ .items-grid::-webkit-scrollbar-track {
86
+ background-color: var(--bg-primary);
87
+ }
frontend/src/components/shared/Sections/ScrollSection.js ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { useRef, useState, useEffect } from "react";
3
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4
+ import { faCaretLeft, faCaretRight } from "@fortawesome/free-solid-svg-icons";
5
+ import "./ScrollSection.css";
6
+
7
+ const ScrollSection = ({ title, children }) => {
8
+ const scrollRef = useRef(null);
9
+ const [canScrollLeft, setCanScrollLeft] = useState(false);
10
+ const [canScrollRight, setCanScrollRight] = useState(true);
11
+
12
+ useEffect(() => {
13
+ const currentScrollRef = scrollRef.current;
14
+ const firstChild = currentScrollRef?.firstElementChild;
15
+ const lastChild = currentScrollRef?.lastElementChild;
16
+
17
+ if (!firstChild || !lastChild) return;
18
+
19
+ const observerOptions = {
20
+ root: currentScrollRef,
21
+ threshold: 1.0
22
+ };
23
+
24
+ const callback = (entries) => {
25
+ entries.forEach((entry) => {
26
+ if (entry.target === firstChild) {
27
+ setCanScrollLeft(!entry.isIntersecting);
28
+ } else if (entry.target === lastChild) {
29
+ setCanScrollRight(!entry.isIntersecting);
30
+ }
31
+ });
32
+ };
33
+
34
+ const observer = new IntersectionObserver(callback, observerOptions);
35
+
36
+ observer.observe(firstChild);
37
+ observer.observe(lastChild);
38
+
39
+ return () => {
40
+ observer.disconnect();
41
+ };
42
+ }, [children]);
43
+
44
+ const scroll = (direction) => {
45
+ if (scrollRef.current) {
46
+ const scrollAmount = direction === "left" ? -360 : 360;
47
+ scrollRef.current.scrollBy({ left: scrollAmount, behavior: "smooth" });
48
+ }
49
+ };
50
+
51
+ return (
52
+ <section className="scroll-section">
53
+ <div className="scroll-section-controls">
54
+ <h2>{title}</h2>
55
+ <div className="scroll-controls">
56
+ <button
57
+ onClick={() => scroll("left")}
58
+ className="scroll-button"
59
+ disabled={!canScrollLeft}
60
+ >
61
+ <FontAwesomeIcon icon={faCaretLeft} size="2xl" />
62
+ </button>
63
+ <button
64
+ onClick={() => scroll("right")}
65
+ className="scroll-button"
66
+ disabled={!canScrollRight}
67
+ >
68
+ <FontAwesomeIcon icon={faCaretRight} size="2xl" />
69
+ </button>
70
+ </div>
71
+ </div>
72
+ <div className="items-grid" ref={scrollRef}>
73
+ {children}
74
+ </div>
75
+ </section>
76
+ );
77
+ };
78
+
79
+ export default ScrollSection;
frontend/src/components/{Sidebar.css β†’ shared/Sidebar/Sidebar.css} RENAMED
File without changes
frontend/src/components/{Sidebar.js β†’ shared/Sidebar/Sidebar.js} RENAMED
File without changes
frontend/src/components/{Spinner.css β†’ shared/Spinner/Spinner.css} RENAMED
File without changes
frontend/src/components/{Spinner.js β†’ shared/Spinner/Spinner.js} RENAMED
File without changes
frontend/src/lib/config.js CHANGED
@@ -1,7 +1,7 @@
1
  const config = {
2
  apiBaseUrl: 'https://hans-den-load-balancer.hf.space',
3
  searchUrl: 'https://unicone-studio-search.hf.space/api/search',
4
- version: "0.0.0.5 Alpha",
5
  };
6
 
7
  export default config;
 
1
  const config = {
2
  apiBaseUrl: 'https://hans-den-load-balancer.hf.space',
3
  searchUrl: 'https://unicone-studio-search.hf.space/api/search',
4
+ version: "0.0.0.6 Alpha",
5
  };
6
 
7
  export default config;
frontend/src/modals/{GenreFilterModal.css β†’ Genre/GenreFilterModal.css} RENAMED
File without changes
frontend/src/modals/{GenreFilterModal.js β†’ Genre/GenreFilterModal.js} RENAMED
File without changes
frontend/src/skeletons/{movieCard.css β†’ Card/movieCard.css} RENAMED
@@ -51,13 +51,12 @@
51
 
52
  @keyframes pulse {
53
  0% {
54
- background-color: #1b1c24;
55
  }
56
  50% {
57
- background-color: #323443;
58
  }
59
  100% {
60
- background-color: #1b1c24;
61
  }
62
- }
63
-
 
51
 
52
  @keyframes pulse {
53
  0% {
54
+ background-color: var(--bg-primary);
55
  }
56
  50% {
57
+ background-color: var(--bg-secondary);
58
  }
59
  100% {
60
+ background-color: var(--bg-primary);
61
  }
62
+ }
 
frontend/src/skeletons/{movieCard.js β†’ Card/movieCard.js} RENAMED
@@ -1,5 +1,5 @@
1
  import React from 'react';
2
- import '@/skeletons/movieCard.css';
3
 
4
  const SkeletonLoader = () => {
5
  return (
 
1
  import React from 'react';
2
+ import './movieCard.css';
3
 
4
  const SkeletonLoader = () => {
5
  return (
frontend/src/skeletons/{HeroSection.css β†’ Sections/HeroSection.css} RENAMED
@@ -3,14 +3,15 @@
3
  .skeleton-container {
4
  position: relative;
5
  width: 100dvw;
6
- height: 480px; /* Set height to auto for responsiveness */
7
  display: flex;
8
  flex-direction: column;
9
  justify-content: flex-end;
10
- background-color: #1b1d2b; /* Dark purple background */
11
  padding: 1rem;
12
  padding-bottom: 2rem;
13
- overflow: hidden;
 
14
  }
15
 
16
  .skeleton-image {
@@ -18,7 +19,7 @@
18
  height: 100%; /* Adjust height to fit container */
19
  max-height: 400px; /* Maximum height to maintain aspect ratio */
20
  border-radius: 4px;
21
- background: linear-gradient(90deg, #3b3f5c 25%, #2b2d43 50%, #3b3f5c 75%);
22
  background-size: 200% 100%;
23
  animation: shimmer 1.5s infinite;
24
  }
@@ -33,7 +34,7 @@
33
  }
34
 
35
  .skeleton-title, .skeleton-description {
36
- background: linear-gradient(90deg, #3b3f5c 25%, #2b2d43 50%, #3b3f5c 75%);
37
  background-size: 200% 100%;
38
  border-radius: 4px;
39
  animation: shimmer 1.5s infinite;
 
3
  .skeleton-container {
4
  position: relative;
5
  width: 100dvw;
6
+ height: 480px;
7
  display: flex;
8
  flex-direction: column;
9
  justify-content: flex-end;
10
+ background-color: var(--bg-primary); /* Dark purple background */
11
  padding: 1rem;
12
  padding-bottom: 2rem;
13
+ overflow: visible;
14
+ z-index: -1;
15
  }
16
 
17
  .skeleton-image {
 
19
  height: 100%; /* Adjust height to fit container */
20
  max-height: 400px; /* Maximum height to maintain aspect ratio */
21
  border-radius: 4px;
22
+ background: linear-gradient(90deg, var(--bg-primary) 25%, var(--bg-secondary) 50%, var(--bg-primary) 75%);
23
  background-size: 200% 100%;
24
  animation: shimmer 1.5s infinite;
25
  }
 
34
  }
35
 
36
  .skeleton-title, .skeleton-description {
37
+ background: linear-gradient(90deg, var(--bg-primary) 25%, var(--bg-secondary) 50%, var(--bg-primary) 75%);
38
  background-size: 200% 100%;
39
  border-radius: 4px;
40
  animation: shimmer 1.5s infinite;
frontend/src/skeletons/{HeroSection.js β†’ Sections/HeroSection.js} RENAMED
@@ -1,4 +1,3 @@
1
- // SkeletonLoader.js
2
  import './HeroSection.css';
3
 
4
  const SkeletonLoader = () => {
 
 
1
  import './HeroSection.css';
2
 
3
  const SkeletonLoader = () => {