diff --git a/.babelrc.js b/.babelrc.js
new file mode 100644
index 00000000..951ad207
--- /dev/null
+++ b/.babelrc.js
@@ -0,0 +1,4 @@
+module.exports = {
+ presets: ['@babel/preset-env'],
+ plugins: [['@babel/plugin-transform-runtime', { corejs: 3 }]],
+}
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 00000000..8dc16615
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,11 @@
+{
+ "extends": [
+ "eslint:recommended",
+ "plugin:import/recommended",
+ "plugin:prettier/recommended"
+ ],
+ "plugins": [
+ "import",
+ "prettier"
+ ]
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..30001661
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+.vscode
+dist
+
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..6c99e89f
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,9 @@
+{
+ "semi": false,
+ "singleQuote": true,
+ "endOfLine": "lf",
+ "singleAttributePerLine": true,
+ "bracketSameLine": true,
+ "trailingComma": "none",
+ "arrowParens": "avoid"
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 32dc87a5..027c9415 100644
--- a/README.md
+++ b/README.md
@@ -1,219 +1,62 @@
-# π¬ μν κ²μ
-
-μ£Όμ΄μ§ APIλ₯Ό νμ©ν΄ '[μμ± μμ](https://stupefied-hodgkin-d9d350.netlify.app/)' μ²λΌ μμ λ‘κ² μν κ²μ κΈ°λ₯μ ꡬνν΄λ³΄μΈμ!
-κ³Όμ μν λ° λ¦¬λ·° κΈ°κ°μ λ³λ 곡μ§λ₯Ό μ°Έκ³ νμΈμ!
-
-## κ³Όμ μν λ° μ μΆ λ°©λ²
-
-```
-KDTκΈ°μλ²νΈ_μ΄λ¦ | E.g, KDT0_ParkYoungWoong
-```
-
-1. νμ¬ μ μ₯μλ₯Ό λ‘컬μ ν΄λ‘ (Clone)ν©λλ€.
-1. μμ μ λ³Έλͺ
μΌλ‘ λΈλμΉλ₯Ό μμ±ν©λλ€.(κ΅¬λΆ κ°λ₯νλλ‘ λ³Έλͺ
μ κΌ νμ€μΉΌμΌμ΄μ€λ‘ νμνμΈμ, `git branch KDT0_ParkYoungWoong`)
-1. μμ μ λ³Έλͺ
λΈλμΉμμ κ³Όμ λ₯Ό μνν©λλ€.
-1. κ³Όμ μνμ΄ μλ£λλ©΄, μμ μ λ³Έλͺ
λΈλμΉλ₯Ό μ격 μ μ₯μμ νΈμ(Push)ν©λλ€.(`main` λΈλμΉμ νΈμνμ§ μλλ‘ κΌ μ£ΌμνμΈμ, `git push origin KDT0_ParkYoungWoong`)
-1. μ μ₯μμμ `main` λΈλμΉλ₯Ό λμμΌλ‘ Pull Request μμ±νλ©΄, κ³Όμ μ μΆμ΄ μλ£λ©λλ€!(E.g, `main` <== `KDT0_ParkYoungWoong`)
-
-- `main` νΉμ λ€λ₯Έ μ¬λμ λΈλμΉλ‘ μ λ λ³ν©νμ§ μλλ‘ μ£ΌμνμΈμ!
-- Pull Requestμμ 보μ΄λ μ€λͺ
μ λ€λ₯Έ μ¬λλ€μ΄ μ΄ν΄νκΈ° μ½λλ‘ κΌΌκΌΌνκ² μμ±νμΈμ!
-- Pull Requestμμ κ³Όμ μ μΆ ν μ λ λ³ν©(Merge)νμ§ μλλ‘ μ£ΌμνμΈμ!
-- κ³Όμ μν λ° μ μΆ κ³Όμ μμ λ¬Έμ κ° λ°μν κ²½μ°, λ°λ‘ λ΄λΉ λ©ν λ κ°μ¬μμ μκΈ°νμΈμ!
-
-## μꡬμ¬ν
-
-νμ μꡬμ¬νμ κΌ λ¬μ±ν΄μΌ νλ λͺ©νλ‘, μμ /μμ λ λΆκ°νκ³ μΆκ°λ κ°λ₯ν©λλ€.
-μ ν μꡬμ¬νμ λ¨μ μμλ‘, μμ λ‘κ² μΆκ°/μμ /μμ ν΄μ ꡬνν΄λ³΄μΈμ.
-κ° μꡬμ¬νμ λ¬μ± ν λ§ν¬λ€μ΄μμ `- [x]`λ‘ νμνμΈμ.
-
-### β νμ
-
-- [ ] μν μ λͺ©μΌλ‘ κ²μμ΄ κ°λ₯ν΄μΌ ν©λλ€!
-- [ ] κ²μλ κ²°κ³Όμ μν λͺ©λ‘μ΄ μΆλ ₯λΌμΌ ν©λλ€!
-- [ ] λ¨μΌ μνμ μμΈμ 보(μ λͺ©, κ°λ΄μ°λ, νμ , μ₯λ₯΄, κ°λ
, λ°°μ°, μ€κ±°λ¦¬, ν¬μ€ν° λ±)λ₯Ό λ³Ό μ μμ΄μΌ ν©λλ€!
-- [ ] μ€μ μλΉμ€λ‘ λ°°ν¬νκ³ μ κ·Ό κ°λ₯ν λ§ν¬λ₯Ό μΆκ°ν΄μΌ ν©λλ€.
-
-### β μ ν
-
-- [ ] ν λ²μ κ²μμΌλ‘ μν λͺ©λ‘μ΄ 20κ° μ΄μ κ²μλλλ‘ λ§λ€μ΄λ³΄μΈμ.
-- [ ] μν κ°λ΄μ°λλ‘ κ²μν μ μλλ‘ λ§λ€μ΄λ³΄μΈμ.
-- [ ] μν λͺ©λ‘μ κ²μνλ λμ λ‘λ© μ λλ©μ΄μ
μ΄ λ³΄μ΄λλ‘ λ§λ€μ΄λ³΄μΈμ.
-- [ ] 무ν μ€ν¬λ‘€ κΈ°λ₯μ μΆκ°ν΄μ μΆκ° μν λͺ©λ‘μ λ³Ό μ μλλ‘ λ§λ€μ΄λ³΄μΈμ.
-- [ ] μν ν¬μ€ν°κ° μμ κ²½μ° λ체 μ΄λ―Έμ§λ₯Ό μΆλ ₯νλλ‘ λ§λ€μ΄λ³΄μΈμ.
-- [ ] μν μμΈμ λ³΄κ° μΆλ ₯λκΈ° μ μ λ‘λ© μ λλ©μ΄μ
μ΄ λ³΄μ΄λλ‘ λ§λ€μ΄λ³΄μΈμ.
-- [ ] μν μμΈμ 보 ν¬μ€ν°λ₯Ό κ³ ν΄μλλ‘ μΆλ ₯ν΄λ³΄μΈμ. (μ€μκ° μ΄λ―Έμ§ 리μ¬μ΄μ§)
-- [ ] μ°¨λ³νκ° κ°λ₯νλλ‘ νλ‘μ νΈλ₯Ό μ΅λν μμκ² λ§λ€μ΄λ³΄μΈμ.
-- [ ] μνμ κ΄λ ¨λ κΈ°ν κΈ°λ₯λ κ³ λ €ν΄λ³΄μΈμ.
-
-## API κΈ°λ³Έ μ¬μ©λ²
-
-```curl
-curl https://omdbapi.com/?apikey=7035c60c
- \ -X 'GET'
-```
-
-## μν λͺ©λ‘ κ²μ
-
-μν λͺ©λ‘μ ν λ²μ μ΅λ 10κ°κΉμ§ κ²μν μ μμ΅λλ€.
-
-νλΌλ―Έν° | μ€λͺ
| κΈ°λ³Έκ°
----|----------------------|---
-`s` | κ²μν μν μ λͺ©(νμ!) | -
-`y` | κ²μν κ°λ΄μ°λ, λΉ κ°μ μ 체 κ²μ | -
-`page` | κ²μν νμ΄μ§ λ²νΈ | `1`
-
-μμ² μ½λ μμ:
-
-```js
-async function getMovies(title, year = '', page = 1) {
- const s = `&s=${title}`
- const y = `&y=${year}`
- const p = `&page=${page}`
- try {
- const res = await fetch(`https://omdbapi.com/?apikey=7035c60c${s}${y}${p}`)
- const json = await res.json()
- if (json.Response === 'True') {
- const { Search: movies, totalResults } = json
- return {
- movies,
- totalResults
- }
- }
- return json.Error
- } catch (error) {
- console.log(error)
- }
-}
-```
-
-μλ΅ λ°μ΄ν° νμ
λ° μμ:
-
-```ts
-interface ResponseValue {
- Search: Movie[] // κ²μλ μν λͺ©λ‘, μ΅λ 10κ°
- totalResults: string // κ²μλ μν κ°μ
- Response: 'True' | 'False' // μμ² μ±κ³΅ μ¬λΆ
-}
-interface Movie {
- Title: string // μν μ λͺ©
- Year: string // μν κ°λ΄μ°λ
- imdbID: string // μν κ³ μ ID
- Type: string // μν νμ
- Poster: string // μν ν¬μ€ν° μ΄λ―Έμ§ URL
-}
-```
-
-```json
-{
- "Search": [
- {
- "Title": "Frozen",
- "Year": "2013",
- "imdbID": "tt2294629",
- "Type": "movie",
- "Poster": "https://m.media-amazon.com/images/M/MV5BMTQ1MjQwMTE5OF5BMl5BanBnXkFtZTgwNjk3MTcyMDE@._V1_SX300.jpg"
- },
- {
- "Title": "Frozen II",
- "Year": "2019",
- "imdbID": "tt4520988",
- "Type": "movie",
- "Poster": "https://m.media-amazon.com/images/M/MV5BMjA0YjYyZGMtN2U0Ni00YmY4LWJkZTItYTMyMjY3NGYyMTJkXkEyXkFqcGdeQXVyNDg4NjY5OTQ@._V1_SX300.jpg"
- }
- ],
- "totalResults": "338",
- "Response": "True"
-}
-```
-
-## μν μμ μ 보 κ²μ
-
-λ¨μΌ μνμ μμ μ 보λ₯Ό κ²μν©λλ€.
-
-νλΌλ―Έν° | μ€λͺ
| κΈ°λ³Έκ°
----|---|---
-`i` | κ²μν μν ID(νμ!) |
-`plot` | μ€κ±°λ¦¬ κΈΈμ΄ | `short`
-
-μμ² μ½λ μμ:
-
-```js
-async function getMovie(id) {
- const res = await fetch(`https://omdbapi.com/?apikey=7035c60c&i=${id}&plot=full`)
- const json = await res.json()
- if (json.Response === 'True') {
- return json
- }
- return json.Error
-}
-```
-
-μλ΅ λ°μ΄ν° νμ
λ° μμ:
-
-```ts
-interface ResponseValue {
- Title: string // μν μ λͺ©
- Year: string // μν κ°λ΄μ°λ
- Rated: string // μν λ±κΈ
- Released: string // μν κ°λ΄μΌ
- Runtime: string // μν μμμκ°
- Genre: string // μν μ₯λ₯΄
- Director: string // μν κ°λ
- Writer: string // μν μκ°
- Actors: string // μν μΆμ°μ§
- Plot: string // μν μ€κ±°λ¦¬
- Language: string // μν μΈμ΄
- Country: string // μν μ μ κ΅κ°
- Awards: string // μν μμ λ΄μ
- Poster: string // μν ν¬μ€ν° μ΄λ―Έμ§ URL
- Ratings: Rating[] // μν νμ μ 보
- Metascore: string // μν λ©νμ€μ½μ΄
- imdbRating: string // μν IMDB νμ
- imdbVotes: string // μν IMDB ν¬ν μ
- imdbID: string // μν κ³ μ ID
- Type: string // μν νμ
- DVD: string // μν DVD μΆμμΌ
- BoxOffice: string // μν λ°μ€μ€νΌμ€
- Production: string // μν μ μμ¬
- Website: string // μν 곡μ μΉμ¬μ΄νΈ
- Response: string // μμ² μ±κ³΅ μ¬λΆ
-}
-interface Rating { // μν νμ μ 보
- Source: string // νμ μ 곡 μ¬μ΄νΈ
- Value: string // νμ
-}
-```
-
-```json
-{
- "Title": "Frozen",
- "Year": "2013",
- "Rated": "PG",
- "Released": "27 Nov 2013",
- "Runtime": "102 min",
- "Genre": "Animation, Adventure, Comedy",
- "Director": "Chris Buck, Jennifer Lee",
- "Writer": "Jennifer Lee, Hans Christian Andersen, Chris Buck",
- "Actors": "Kristen Bell, Idina Menzel, Jonathan Groff",
- "Plot": "When the newly crowned Queen Elsa accidentally uses her power to turn things into ice to curse her home in infinite winter, her sister Anna teams up with a mountain man, his playful reindeer, and a snowman to change the weather co...",
- "Language": "English, Norwegian",
- "Country": "United States",
- "Awards": "Won 2 Oscars. 82 wins & 60 nominations total",
- "Poster": "https://m.media-amazon.com/images/M/MV5BMTQ1MjQwMTE5OF5BMl5BanBnXkFtZTgwNjk3MTcyMDE@._V1_SX300.jpg",
- "Ratings": [
- { "Source": "Internet Movie Database", "Value": "7.4/10" },
- { "Source": "Rotten Tomatoes", "Value": "90%" },
- { "Source": "Metacritic", "Value": "75/100" }
- ],
- "Metascore": "75",
- "imdbRating": "7.4",
- "imdbVotes": "620,489",
- "imdbID": "tt2294629",
- "Type": "movie",
- "DVD": "18 Mar 2014",
- "BoxOffice": "$400,953,009",
- "Production": "N/A",
- "Website": "N/A",
- "Response": "True"
-}
-```
+## π¬ 2μ°¨κ³Όμ : APIλ₯Ό νμ©ν μνκ²μ μ¬μ΄νΈ λ§λ€κΈ°
+
+> μμ±μ : KDT5 κΉλ€μ¬ - 4μ‘°
+
+### [κ²°κ³Όλ¬Ό](https://ephemeral-pastelito-792562.netlify.app/#/)
+> HTML, SCSS, JS, Webpack νμ©
+
+
+
+## νμ μꡬμ¬ν
+
+- [x] μν μ λͺ©μΌλ‘ κ²μμ΄ κ°λ₯ν΄μΌ ν©λλ€!
+- [x] κ²μλ κ²°κ³Όμ μν λͺ©λ‘μ΄ μΆλ ₯λΌμΌ ν©λλ€!
+- [x] λ¨μΌ μνμ μμΈμ 보(μ λͺ©, κ°λ΄μ°λ, νμ , μ₯λ₯΄, κ°λ
, λ°°μ°, μ€κ±°λ¦¬, ν¬μ€ν° λ±)λ₯Ό λ³Ό μ μμ΄μΌ ν©λλ€!
+- [x] μ€μ μλΉμ€λ‘ λ°°ν¬νκ³ μ κ·Ό κ°λ₯ν λ§ν¬λ₯Ό μΆκ°ν΄μΌ ν©λλ€.
+
+
+
+## μ ν μꡬμ¬ν
+- [x] ν λ²μ κ²μμΌλ‘ μν λͺ©λ‘μ΄ 20κ° μ΄μ κ²μλλλ‘ λ§λ€μ΄λ³΄μΈμ.
+- [x] μν κ°λ΄μ°λλ‘ κ²μν μ μλλ‘ λ§λ€μ΄λ³΄μΈμ.
+- [x] μν λͺ©λ‘μ κ²μνλ λμ λ‘λ© μ λλ©μ΄μ
μ΄ λ³΄μ΄λλ‘ λ§λ€μ΄λ³΄μΈμ.
+- [x] 무ν μ€ν¬λ‘€ κΈ°λ₯μ μΆκ°ν΄μ μΆκ° μν λͺ©λ‘μ λ³Ό μ μλλ‘ λ§λ€μ΄λ³΄μΈμ.
+- [x] μν ν¬μ€ν°κ° μμ κ²½μ° λ체 μ΄λ―Έμ§λ₯Ό μΆλ ₯νλλ‘ λ§λ€μ΄λ³΄μΈμ.
+- [x] μν μμΈμ λ³΄κ° μΆλ ₯λκΈ° μ μ λ‘λ© μ λλ©μ΄μ
μ΄ λ³΄μ΄λλ‘ λ§λ€μ΄λ³΄μΈμ.
+- [x] μν μμΈμ 보 ν¬μ€ν°λ₯Ό κ³ ν΄μλλ‘ μΆλ ₯ν΄λ³΄μΈμ. (μ€μκ° μ΄λ―Έμ§ 리μ¬μ΄μ§)
+- [x] μ°¨λ³νκ° κ°λ₯νλλ‘ νλ‘μ νΈλ₯Ό μ΅λν μμκ² λ§λ€μ΄λ³΄μΈμ.
+- [x] μνμ κ΄λ ¨λ κΈ°ν κΈ°λ₯λ κ³ λ €ν΄λ³΄μΈμ.
+
+
+
+## νλ©΄ & ꡬνν λΆλΆ
+
+1. μμνμ΄μ§
+
+μ²μ μ μνλ©΄ 보μ¬μ§λ νμ΄μ§μ΄λ©°,
+μνμ κ΄λ ¨λ κΈ°ν κΈ°λ₯μΌλ‘
+λͺ©λ‘ μ€ μΆμ² λ°κΈΈ μνλ μ₯λ₯΄λ₯Ό μ ννλ©΄ μΈμ
μ€ν 리μ§μ μ μ₯ν΄ λ€μ νλ©΄μμ μ νν μ₯λ₯΄μ μνλͺ©λ‘λ€μ΄ 보μ¬μ§κ² λ©λλ€.
+
+2. λ©μΈνμ΄μ§
+
+
+μμ νμ΄μ§μμ λ²νΌμ λλ₯΄λ©΄ λμ€λ νμ΄μ§λ‘ μλ¨μλ μλ‘ κ°λ΄ν μνμ λν μκ³ νΈκ³Ό μ 보λ₯Ό 보μ¬μ£Όκ³ μ€μμλ μ νν μ₯λ₯΄μ λν μΆμ² λͺ©λ‘μ 보μ¬μ£Όκ³ νλ¨μλ κ²μν μ μλ κ²μμ°½μ΄ λνλ©λλ€.
+μνλ₯Ό κ²μν μ μκ³ , ν¬μ€ν°κ° μλ μνλ λ체 μ΄λ―Έμ§κ° μΆλ ₯λ©λλ€. 무ν μ€ν¬λ‘€μ ν΅ν΄ κ²μν μνμ λν λͺ¨λ λͺ©λ‘μ μ‘°νν μ μμ΅λλ€.
+
+3. μμΈ μ 보 μΆλ ₯
+
+μλ¨μ μΆλ ₯λ μμμ more λ²νΌμ΄λ, μΆμ²λ μν λͺ©λ‘μ ν΄λ¦νκ±°λ κ²μλ μνλ₯Ό ν΄λ¦νλ©΄ λνλλ μνμ λν μμΈ μ 보λ₯Ό λͺ¨λ¬μ°½μΌλ‘ 보μ¬μ€λλ€.
+
+4. NotFound νμ΄μ§
+
+λ±λ‘λ νμ΄μ§ μΈμ μ£Όμλ‘ μ κ·Όνλ©΄ 보μ¬μ£Όλ νμ΄μ§ μ
λλ€.
+
+
+
+## μμ¬μ΄ λΆλΆ
+
+1. μ²μμλ λ©μΈ νμ΄μ§ μλ¨μ μμμ΄ λ€μ΄κ° μ¬λΌμ΄λλ₯Ό ꡬννμμΌλ, λΉλμ€μ μ¬λΌμ΄λμ κ΄λ ¨λ μ§μ λΆμ‘±μΌλ‘ κΈ°μ‘΄μ ꡬννλ €κ³ νλ κ° μ¬λΌμ΄λμ μμμ΄ μ¬μμλ£ λλ©΄ λ€μ μ¬λΌμ΄λλ‘ λμ΄κ°λ κΈ°λ₯μ ꡬννμ§ λͺ»νκ³ , μ¬λΌμ΄λ λν μμμ μΆλ ₯λλ μλ μ¬λΌμ΄λκ° μ λμ§ μλ μΌμ΄ λ°μνκ³ , μ¬λΌμ΄λκ° μμ±λ λλ§λ€ μ 보λ₯Ό λΆλ¬μμκΈ° λλ¬Έμ μ±λ₯ λ¬Έμ κ° μ겨 μ¬λΌμ΄λλ₯Ό μμ νκ³ λ¨μΌ μμμΌλ‘ λ체νκ² λμμ΅λλ€.
+
+2. κΈ°λ₯ ꡬνμλ§ κΈν΄ μ 보λ₯Ό λ°μμ¨ ν μ¬λΌμ΄λλ€μ΄ μμ±λκ³ μΆλ ₯λ λμ μλ, μ±λ₯ λ±μ λν΄ μκ°νμ§ λͺ»νλ μ μ΄ μμ¬μ κ³ , μ΅λν μμ νμΌλ apiμμ², λΉλκΈ°μ λν μ΄ν΄κ° μμ§ λ§μ΄ λΆμ‘±νλ€λ κ²μ μκ²λμμ΅λλ€.
+
+3. μνλ₯Ό κ²μν ν μ€ν¬λ‘€μ λ΄λ¦¬λ©΄ 무νμ€ν¬λ‘€λ‘ κ²μν λͺ¨λ μ 보λ₯Ό λ³Ό μ μλλ° μ€ν¬λ‘€μ λ‘λ©μ κΈ°λ€λ¦¬μ§ μκ³ κΈνκ² λ΄λ¦¬κ±°λ νλ©΄ κ²μ λͺ©λ‘μ΄ μ¨μ νμ§ μμ λΆλΆμ΄ μκΈ°λ κ²½μ°μλν΄ ν΄κ²°μ νμ§ λͺ»νμ΅λλ€.
\ No newline at end of file
diff --git a/assets/resource/Internet Movie Database.png b/assets/resource/Internet Movie Database.png
new file mode 100644
index 00000000..e6ca3c86
Binary files /dev/null and b/assets/resource/Internet Movie Database.png differ
diff --git a/assets/resource/Metacritic.png b/assets/resource/Metacritic.png
new file mode 100644
index 00000000..761d2b39
Binary files /dev/null and b/assets/resource/Metacritic.png differ
diff --git a/assets/resource/Rotten Tomatoes.png b/assets/resource/Rotten Tomatoes.png
new file mode 100644
index 00000000..37dd7e5d
Binary files /dev/null and b/assets/resource/Rotten Tomatoes.png differ
diff --git a/assets/resource/favicon.png b/assets/resource/favicon.png
new file mode 100644
index 00000000..73b2b3b8
Binary files /dev/null and b/assets/resource/favicon.png differ
diff --git a/assets/resource/noimage.png b/assets/resource/noimage.png
new file mode 100644
index 00000000..b8ff8ccf
Binary files /dev/null and b/assets/resource/noimage.png differ
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..3914fb60
--- /dev/null
+++ b/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+ `
+ }
+
+ const closeButton = this.el.querySelector('.btn-close')
+ closeButton.addEventListener('click', () => {
+ const body = document.querySelector('body')
+ body.classList.remove('scroll-hidden')
+ movieStore.state.modal = false
+ movieStore.state.contents = false
+ })
+ }
+ }
+}
diff --git a/src/components/MovieItem.js b/src/components/MovieItem.js
new file mode 100644
index 00000000..86708b3c
--- /dev/null
+++ b/src/components/MovieItem.js
@@ -0,0 +1,37 @@
+import { Component } from '../core/core'
+import movieStore, { getMovieDetails } from '../store/movie'
+
+export default class MovieItem extends Component {
+ constructor(props) {
+ super({
+ props,
+ tagName: 'div'
+ })
+ }
+ render() {
+ const { movie } = this.props
+ this.el.classList.add('movie')
+ this.el.innerHTML = /*HTML*/ `
+
+
+ ${movie.Title}
+
+ `
+
+ this.el.addEventListener('click', async () => {
+ movieStore.state.movie = {}
+ movieStore.state.contents = false
+ movieStore.state.modal = true
+ await getMovieDetails(movie.imdbID)
+ })
+ }
+}
diff --git a/src/components/MovieList.js b/src/components/MovieList.js
new file mode 100644
index 00000000..5596cebc
--- /dev/null
+++ b/src/components/MovieList.js
@@ -0,0 +1,47 @@
+import { Component } from '../core/core'
+import movieStore, { searchMovies } from '../store/movie'
+import MovieItem from './MovieItem'
+import MovieDetail from './MovieDetail'
+import TheLoader from './TheLoader'
+
+export default class MovieList extends Component {
+ constructor() {
+ super()
+ movieStore.subscribe('movies', () => {
+ this.render()
+ })
+ movieStore.subscribe('listLoading', () => {
+ this.render()
+ })
+ movieStore.subscribe('message', () => {
+ this.render()
+ })
+ }
+ render() {
+ this.el.classList.add('movie-list')
+ this.el.innerHTML = /*HTML*/ `
+ ${
+ movieStore.state.message
+ ? `
${movieStore.state.message}
`
+ : '
'
+ }
+ `
+ const loader = new TheLoader().el
+ const detail = new MovieDetail().el
+ this.el.append(loader, detail)
+
+ movieStore.state.listLoading
+ ? loader.classList.remove('hide')
+ : loader.classList.add('hide')
+
+ const moviesEl = this.el.querySelector('.movies')
+ moviesEl?.append(
+ ...movieStore.state.movies.map(
+ movie =>
+ new MovieItem({
+ movie
+ }).el
+ )
+ )
+ }
+}
diff --git a/src/components/ObserverEl.js b/src/components/ObserverEl.js
new file mode 100644
index 00000000..b4831154
--- /dev/null
+++ b/src/components/ObserverEl.js
@@ -0,0 +1,30 @@
+import { Component } from '../core/core'
+import movieStore, { searchMovies } from '../store/movie'
+
+export default class ObserverEl extends Component {
+ render() {
+ this.el.classList.add('observer-container')
+ this.el.innerHTML = /*HTML*/ `
+
+ `
+
+ const io = new IntersectionObserver(handleObserver, {
+ threshold: 0.5
+ })
+ function handleObserver(entries) {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ if (movieStore.state.page < movieStore.state.pageMax) {
+ movieStore.state.page += 1
+ searchMovies(movieStore.state.page)
+ if (movieStore.state.page === movieStore.state.pageMax) {
+ io.disconnect()
+ }
+ }
+ }
+ })
+ }
+ const observer = this.el.querySelector('#observe')
+ io.observe(observer)
+ }
+}
diff --git a/src/components/Particle.js b/src/components/Particle.js
new file mode 100644
index 00000000..a47e827d
--- /dev/null
+++ b/src/components/Particle.js
@@ -0,0 +1,15 @@
+import { Component } from '../core/core'
+
+export default class Particle extends Component {
+ render() {
+ this.el.classList.add('animation-wrapper')
+
+ const part = 4
+
+ for (let i = 1; i <= part; i += 1) {
+ const part = document.createElement('div')
+ part.setAttribute('class', `particle particle-${i}`)
+ this.el.append(part)
+ }
+ }
+}
diff --git a/src/components/Search.js b/src/components/Search.js
new file mode 100644
index 00000000..8fe6381f
--- /dev/null
+++ b/src/components/Search.js
@@ -0,0 +1,55 @@
+import { Component } from '../core/core'
+import movieStore, { searchMovies } from '../store/movie'
+
+export default class Search extends Component {
+ render() {
+ this.el.classList.add('search')
+
+ this.el.innerHTML = /*HTML*/ `
+
+
+
+ All Year
+
+ Search
+
+ `
+
+ const thisYear = new Date().getFullYear()
+ const select = this.el.querySelector('#year')
+ for (let i = thisYear; i >= 1985; i--) {
+ const options = document.createElement('option')
+ options.value = i
+ options.innerText = i
+ select.append(options)
+ }
+
+ const inputEl = this.el.querySelector('input')
+ inputEl.addEventListener('input', () => {
+ movieStore.state.searchText = inputEl.value
+ })
+ inputEl.addEventListener('keydown', event => {
+ if (event.key === 'Enter' && movieStore.state.searchText.trim()) {
+ if (select.value === 'All Year') {
+ movieStore.state.searchYear = ''
+ } else {
+ movieStore.state.searchYear = select.value
+ }
+ searchMovies(1)
+ searchMovies(2)
+ }
+ })
+ const btnEl = this.el.querySelector('.btn-search')
+ btnEl.addEventListener('click', () => {
+ if (movieStore.state.searchText.trim()) {
+ if (select.value === 'All Year') {
+ movieStore.state.searchYear = ''
+ } else {
+ movieStore.state.searchYear = select.value
+ }
+ searchMovies(1)
+ searchMovies(2)
+ }
+ })
+ }
+}
diff --git a/src/components/SmallSlide.js b/src/components/SmallSlide.js
new file mode 100644
index 00000000..4cc05c29
--- /dev/null
+++ b/src/components/SmallSlide.js
@@ -0,0 +1,49 @@
+import { Component } from '../core/core'
+import { register } from 'swiper/element/bundle'
+import recommendStore, { getGenre } from '../store/recommend'
+import movieStore from '../store/movie'
+
+export default class SmallSlide extends Component {
+ render() {
+ this.el.classList.add('small-slide')
+
+ getGenre(sessionStorage.getItem('selectedGenre'))
+
+ this.el.innerHTML = /*HTML*/ `
+
Recommend ${recommendStore.state.genreName} Movies
+
+
+ `
+ const swipercontainer = this.el.querySelector('swiper-container')
+
+ const fetchData = async () => {
+ const responses = await Promise.all(
+ recommendStore.state.genreArr.map(id => {
+ return fetch(
+ `https://omdbapi.com?apikey=14c167f8&i=${id}&plot=full`
+ ).then(res => res.json())
+ })
+ )
+ responses.forEach(movie => {
+ const slide = document.createElement('swiper-slide')
+ slide.innerHTML = /*HTML*/ `
+
+ `
+ swipercontainer.append(slide)
+
+ slide.addEventListener('click', async () => {
+ movieStore.state.movie = {}
+ movieStore.state.contents = false
+ movieStore.state.modal = true
+ movieStore.state.movie = movie
+ movieStore.state.contents = true
+ })
+ })
+ }
+ fetchData()
+ register()
+ }
+}
diff --git a/src/components/TheFooter.js b/src/components/TheFooter.js
new file mode 100644
index 00000000..5a519476
--- /dev/null
+++ b/src/components/TheFooter.js
@@ -0,0 +1,23 @@
+import { Component } from '../core/core'
+import aboutStore from '../store/about'
+
+export default class TheFooter extends Component {
+ constructor() {
+ super({
+ tagName: 'footer'
+ })
+ }
+
+ render() {
+ const { github, repository, name } = aboutStore.state
+
+ this.el.innerHTML = /*HTML*/ `
+
+
+ `
+ }
+}
diff --git a/src/components/TheHeader.js b/src/components/TheHeader.js
new file mode 100644
index 00000000..1b861dda
--- /dev/null
+++ b/src/components/TheHeader.js
@@ -0,0 +1,14 @@
+import { Component } from '../core/core'
+
+export default class TheHeader extends Component {
+ constructor() {
+ super({
+ tagName: 'header'
+ })
+ }
+ render() {
+ this.el.innerHTML = /*HTML*/ `
+
OMDbAPI.COM
+ `
+ }
+}
diff --git a/src/components/TheLoader.js b/src/components/TheLoader.js
new file mode 100644
index 00000000..e80b75c4
--- /dev/null
+++ b/src/components/TheLoader.js
@@ -0,0 +1,8 @@
+import { Component } from '../core/core'
+
+export default class TheLoader extends Component {
+ render() {
+ this.el.setAttribute('class', 'the-loader')
+ this.el.classList.add('hide')
+ }
+}
diff --git a/src/components/TopVideo.js b/src/components/TopVideo.js
new file mode 100644
index 00000000..f0e73860
--- /dev/null
+++ b/src/components/TopVideo.js
@@ -0,0 +1,48 @@
+import { Component } from '../core/core'
+import movieStore, { getMovieDetails } from '../store/movie'
+import recommendStore from '../store/recommend'
+
+export default class TopVideo extends Component {
+ render() {
+ this.el.classList.add('video-container')
+
+ const fetchData = async () => {
+ const newmovie = { ...recommendStore.state.newmovies }
+
+ await getMovieDetails(newmovie[2].id)
+ const { movie } = movieStore.state
+
+ this.el.innerHTML = /*HTML*/ `
+
+
+
+
+
+
+
+ ${movieStore.state.muted ? 'volume_off' : 'volume_up'}
+
+
+
+
+
${movie.Title}
+
${movie.Released}
+
+ ${movie.Plot}
+
+
More...
+
+
+ `
+ const moreButton = this.el.querySelector('.btn-more')
+ moreButton.addEventListener('click', () => {
+ movieStore.state.movie = {}
+ movieStore.state.contents = false
+ movieStore.state.modal = true
+ movieStore.state.movie = movie
+ movieStore.state.contents = true
+ })
+ }
+ fetchData()
+ }
+}
diff --git a/src/core/core.js b/src/core/core.js
new file mode 100644
index 00000000..28fd19fa
--- /dev/null
+++ b/src/core/core.js
@@ -0,0 +1,67 @@
+/*Component*/
+export class Component {
+ constructor(payload = {}) {
+ const { tagName = 'div', props = {}, state = {} } = payload
+ this.el = document.createElement(tagName)
+ this.props = props
+ this.state = state
+ this.render()
+ }
+ render() {}
+}
+
+/*Router*/
+function routeRender(routes) {
+ if (!location.hash) {
+ history.replaceState(null, '', '/#/')
+ }
+ const routerView = document.querySelector('router-view')
+ const [hash, queryString = ''] = location.hash.split('?')
+
+ const query = queryString.split('&').reduce((acc, cur) => {
+ const [key, value] = cur.split('=')
+ acc[key] = value
+ return acc
+ }, {})
+ history.replaceState(query, '')
+
+ const currentRoute = routes.find(route =>
+ new RegExp(`${route.path}/?$`).test(hash)
+ )
+ routerView.innerHTML = ''
+ routerView.append(new currentRoute.component().el)
+
+ window.scrollTo(0, 0)
+}
+export function createRouter(routes) {
+ return function () {
+ window.addEventListener('popstate', () => {
+ routeRender(routes)
+ })
+ routeRender(routes)
+ }
+}
+
+/*Store*/
+export class Store {
+ constructor(state) {
+ this.state = {}
+ this.observers = {}
+ for (const key in state) {
+ Object.defineProperty(this.state, key, {
+ get: () => state[key],
+ set: val => {
+ state[key] = val
+ if (Array.isArray(this.observers[key])) {
+ this.observers[key].forEach(observer => observer(val))
+ }
+ }
+ })
+ }
+ }
+ subscribe(key, cb) {
+ Array.isArray(this.observers[key])
+ ? this.observers[key].push(cb)
+ : (this.observers[key] = [cb])
+ }
+}
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 00000000..87de90bd
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,9 @@
+import App from './App'
+import router from './routes'
+import './scss/style.scss'
+
+const root = document.querySelector('#root')
+
+root.append(new App().el)
+
+router()
diff --git a/src/routes/Home.js b/src/routes/Home.js
new file mode 100644
index 00000000..05496d26
--- /dev/null
+++ b/src/routes/Home.js
@@ -0,0 +1,36 @@
+import { Component } from '../core/core'
+import TheHeader from '../components/TheHeader'
+import TheFooter from '../components/TheFooter'
+import TopVideo from '../components/TopVideo'
+import SmallSlide from '../components/SmallSlide'
+import Search from '../components/Search'
+import MovieList from '../components/MovieList'
+import movieStore from '../store/movie'
+import ObserverEl from '../components/ObserverEl'
+
+export default class Home extends Component {
+ render() {
+ this.el.classList.add('page-home')
+
+ const theHeader = new TheHeader().el
+ const theFooter = new TheFooter().el
+ const topvideo = new TopVideo().el
+ const smallslide = new SmallSlide().el
+ const search = new Search().el
+ const movieList = new MovieList().el
+ const observerEl = new ObserverEl().el
+
+ this.el.append(
+ theHeader,
+ topvideo,
+ smallslide,
+ search,
+ movieList,
+ observerEl,
+ theFooter
+ )
+
+ movieStore.state.movies = []
+ movieStore.state.message = 'Search for the movie title'
+ }
+}
diff --git a/src/routes/NotFound.js b/src/routes/NotFound.js
new file mode 100644
index 00000000..b444811c
--- /dev/null
+++ b/src/routes/NotFound.js
@@ -0,0 +1,25 @@
+import { Component } from '../core/core'
+import TheHeader from '../components/TheHeader'
+import TheFooter from '../components/TheFooter'
+import Particle from '../components/Particle'
+
+export default class NotFound extends Component {
+ render() {
+ this.el.classList.add('page-notfound')
+
+ const container = document.createElement('div')
+
+ container.classList.add('notfound-wrap')
+ container.innerHTML = /*HTML*/ `
+
+ Page Not Found
+
+ `
+
+ const particle = new Particle().el
+ const theHeader = new TheHeader().el
+ const theFooter = new TheFooter().el
+
+ this.el.append(particle, theHeader, theFooter, container)
+ }
+}
diff --git a/src/routes/Start.js b/src/routes/Start.js
new file mode 100644
index 00000000..0503e4d5
--- /dev/null
+++ b/src/routes/Start.js
@@ -0,0 +1,65 @@
+import { Component } from '../core/core'
+import recommendStore from '../store/recommend'
+import TheHeader from '../components/TheHeader'
+import TheFooter from '../components/TheFooter'
+import Particle from '../components/Particle'
+
+export default class Start extends Component {
+ render() {
+ this.el.classList.add('page-start')
+
+ this.el.innerHTML = /*HTML*/ `
+
+
+ OMDb API
+ THE OPEN
+ MOVIE DATABASE
+
+
+ The OMDb API is a RESTful web service to obtain movie information,
+ all content and images on the site are contributed and maintained by our users.
+ If you find this service useful, please consider making a one-time donation or become a patron.
+
+
+ `
+
+ const selector = document.createElement('div')
+ selector.classList.add('selector-genre')
+ selector.innerHTML = /*HTML*/ `
+
When you select a genre, we will recommend a movie for you.
+
+ ${recommendStore.state.genres
+ .map(genre => {
+ return /*HTML*/ `
+
+ ${genre.name}
+ `
+ })
+ .join('')}
+
+
Go Search!
+ `
+
+ const theHeader = new TheHeader().el
+ const theFooter = new TheFooter().el
+ const particle = new Particle().el
+
+ this.el.append(theHeader, particle, selector, theFooter)
+
+ const gosearchButton = this.el.querySelector('.btn-gosearch')
+ gosearchButton.addEventListener('click', () => {
+ const selectedGenre = document.querySelector(
+ 'input[name="genre-group"]:checked'
+ ).value
+
+ sessionStorage.setItem('selectedGenre', selectedGenre)
+ })
+ }
+}
diff --git a/src/routes/index.js b/src/routes/index.js
new file mode 100644
index 00000000..e4fa3c4c
--- /dev/null
+++ b/src/routes/index.js
@@ -0,0 +1,10 @@
+import { createRouter } from '../core/core'
+import Start from './Start'
+import Home from './Home'
+import NotFound from './NotFound'
+
+export default createRouter([
+ { path: '#/', component: Start },
+ { path: '#/home', component: Home },
+ { path: '.*', component: NotFound }
+])
diff --git a/src/scss/base/_common.scss b/src/scss/base/_common.scss
new file mode 100644
index 00000000..b9b5c589
--- /dev/null
+++ b/src/scss/base/_common.scss
@@ -0,0 +1,32 @@
+@use './variable';
+
+body {
+ background-color: variable.$color--black;
+ font-family: 'Nanum Gothic', sans-serif;
+}
+
+/*scroll*/
+body::-webkit-scrollbar{
+ width: 12px;
+}
+
+body::-webkit-scrollbar-thumb{
+ background-color: variable.$color--primary;
+}
+
+body::-webkit-scrollbar-track{
+ background-color: variable.$color--black;
+ border: 2px solid variable.$color--white;
+}
+
+.btn {
+ width: 100px;
+ height: 45px;
+ border-radius: 4px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 18px;
+ font-family: 'Poppins', sans-serif;
+ text-decoration: none;
+}
\ No newline at end of file
diff --git a/src/scss/base/_index.scss b/src/scss/base/_index.scss
new file mode 100644
index 00000000..04d3bd2c
--- /dev/null
+++ b/src/scss/base/_index.scss
@@ -0,0 +1,2 @@
+@forward 'variable';
+@forward 'common';
diff --git a/src/scss/base/_variable.scss b/src/scss/base/_variable.scss
new file mode 100644
index 00000000..ce65a8fe
--- /dev/null
+++ b/src/scss/base/_variable.scss
@@ -0,0 +1,29 @@
+$color--white: #ffffff;
+$color--white-50: rgba(255, 255, 255, .5);
+$color--white-30: rgba(255, 255, 255, .3);
+$color--white-20: rgba(255, 255, 255, .2);
+$color--white-10: rgba(255, 255, 255, .1);
+$color--white-5: rgba(255, 255, 255, .05);
+
+$color--black: #000000;
+$color--black-80: rgba(0, 0, 0, .8);
+$color--black-50: rgba(0, 0, 0, .5);
+$color--black-30: rgba(0, 0, 0, .3);
+
+$color--primary: #37fb8f;
+$color--primary-50: rgba(55, 251, 143, .5);
+$color--primary-20: rgba(55, 251, 143, .2);
+$color--primary-10: rgba(55, 251, 143, .1);
+
+$color--point: #ea23e0;
+$color--point-50: rgba(234, 35, 224, .5);
+$color--point-20: rgba(234, 35, 224, .2);
+$color--point-10: rgba(234, 35, 224, .1);
+
+$color--blue: #3cbaea;
+$color--blue-50:rgba(60, 186, 234, .5);
+$color--blue-20:rgba(60, 186, 234, .2);
+$color--blue-10:rgba(60, 186, 234, .1);
+
+
+$color--hover: #089c0a;
\ No newline at end of file
diff --git a/src/scss/components/_detail.scss b/src/scss/components/_detail.scss
new file mode 100644
index 00000000..44cb8271
--- /dev/null
+++ b/src/scss/components/_detail.scss
@@ -0,0 +1,88 @@
+@use '../base';
+
+.wrap {
+ display: flex;
+ position: relative;
+ width: 100%;
+ height: 100%;
+ &.hide {
+ display: none;
+ }
+ .poster-wrap {
+ display: flex;
+ align-items: center;
+ .poster {
+ $width: 300px;
+ width: $width;
+ height: calc($width * 3 / 2);
+ background-size: cover;
+ flex-shrink: 0;
+ }
+ }
+ .specs {
+ flex-grow: 1;
+ padding: 0 20px;
+ font-size: 20px;
+ color: base.$color--white;
+ overflow-y: auto;
+ &::-webkit-scrollbar{
+ width: 12px;
+ }
+ &::-webkit-scrollbar-thumb{
+ background-color: base.$color--primary;
+ }
+ &::-webkit-scrollbar-track{
+ background-color: base.$color--black;
+ border: 2px solid base.$color--white;
+ }
+ .title {
+ font-size: 48px;
+ color: base.$color--point;
+ font-family: 'Space Mono', monospace;
+ }
+ .labels {
+ margin-top: 10px;
+ color: base.$color--blue;
+ }
+ .plot {
+ margin-top: 10px;
+ }
+ h3 {
+ color: base.$color--primary;
+ font-size: 21px;
+ font-weight: 700;
+ padding: 0;
+ margin: 0;
+ margin-top: 20px;
+ font-family: 'Space Mono', monospace;
+ }
+ p {
+ padding: 0;
+ margin: 0;
+ }
+ .ratings-wrap {
+ margin-bottom: -10px;
+ .ratings {
+ height: 40px;
+ display: flex;
+ .ratings-logo {
+ width: 40px;
+ height: 40px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ margin-right: 10px;
+ display: flex;
+ align-items: center;
+ img {
+ width: 100%;
+ }
+ }
+ p {
+ display: flex;
+ align-items: center;
+ }
+ }
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/scss/components/_footer.scss b/src/scss/components/_footer.scss
new file mode 100644
index 00000000..7156beb9
--- /dev/null
+++ b/src/scss/components/_footer.scss
@@ -0,0 +1,15 @@
+@use '../base';
+
+footer {
+ position: relative;
+ padding: 40px 40px 60px;
+ text-align: center;
+ margin-top: 100px;
+ a {
+ color: base.$color--white-20;
+ text-decoration: none;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/scss/components/_header.scss b/src/scss/components/_header.scss
new file mode 100644
index 00000000..5e9da267
--- /dev/null
+++ b/src/scss/components/_header.scss
@@ -0,0 +1,20 @@
+@use '../base';
+
+header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 60px;
+ background-color: transparent;
+ display: flex;
+ align-items:center;
+ padding: 0 60px;
+ z-index: 3;
+ font-family: 'Poppins', sans-serif;
+ h1 {
+ margin: 0;
+ font-size: 16px;
+ color: base.$color--point;
+ }
+}
\ No newline at end of file
diff --git a/src/scss/components/_index.scss b/src/scss/components/_index.scss
new file mode 100644
index 00000000..966a3adc
--- /dev/null
+++ b/src/scss/components/_index.scss
@@ -0,0 +1,11 @@
+@forward 'header';
+@forward 'particle';
+@forward 'footer';
+@forward 'topvideo';
+@forward 'smallslide';
+@forward 'search';
+@forward 'searchitem';
+@forward 'loader';
+@forward 'modal';
+@forward 'detail';
+@forward 'observer';
diff --git a/src/scss/components/_loader.scss b/src/scss/components/_loader.scss
new file mode 100644
index 00000000..9ae159eb
--- /dev/null
+++ b/src/scss/components/_loader.scss
@@ -0,0 +1,28 @@
+@use '../base';
+
+.the-loader {
+ width: 30px;
+ height: 30px;
+ margin: 30px auto;
+ border: 4px solid base.$color--primary;
+ border-top-color: transparent;
+ border-radius: 50%;
+ animation: loader 2s infinite linear;
+ &.hide {
+ display: none;
+ }
+}
+@keyframes loader {
+ 0% { transform: rotate(0deg); }
+ 100% {transform: rotate(360deg); }
+}
+
+.modal {
+ .the-loader {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ margin-left: -15px;
+ margin-top: -15px;
+ }
+}
\ No newline at end of file
diff --git a/src/scss/components/_modal.scss b/src/scss/components/_modal.scss
new file mode 100644
index 00000000..d795f521
--- /dev/null
+++ b/src/scss/components/_modal.scss
@@ -0,0 +1,40 @@
+@use '../base';
+
+.modal-bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 30;
+ width: 100%;
+ height: 100%;
+ background-color: base.$color--black-80;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ &.hide {
+ display: none;
+ }
+ .modal {
+ width: 60%;
+ height: 70%;
+ position: relative;
+ background-color: base.$color--black;
+ border: 2px solid base.$color--point-50;
+ border-radius: 4px;
+ padding: 40px;
+ .btn-close {
+ padding: 5px;
+ position: absolute;
+ right: 0;
+ top: 0;
+ background-color: transparent;
+ font-size: 21px;
+ border: none;
+ color: base.$color--primary;
+ cursor: pointer;
+ &:hover {
+ color: base.$color--hover;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/scss/components/_observer.scss b/src/scss/components/_observer.scss
new file mode 100644
index 00000000..a3727861
--- /dev/null
+++ b/src/scss/components/_observer.scss
@@ -0,0 +1,7 @@
+.observer-container {
+ width: 100%;
+ #observe {
+ width: 100%;
+ height: 100px;
+ }
+}
\ No newline at end of file
diff --git a/src/scss/components/_particle.scss b/src/scss/components/_particle.scss
new file mode 100644
index 00000000..eeae45dd
--- /dev/null
+++ b/src/scss/components/_particle.scss
@@ -0,0 +1,99 @@
+@use "sass:math";
+
+$color--bg: #000;
+$color--particle: #fff;
+$spacing: 2560px;
+$time-1: 60s;
+$time-2: 120s;
+$time-3: 180s;
+$time-4: 600s;
+
+@function particles($max) {
+ $val: 0px 0px $color--particle;
+ @for $i from 1 through $max {
+ $val: #{$val},
+ random(math.div($spacing, 1px)) * 1px random(math.div($spacing, 1px)) * 1px $color--particle;
+ }
+ @return $val;
+}
+
+@mixin particles($max) {
+ box-shadow: particles($max);
+}
+
+.animation-wrapper {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: $color--bg;
+}
+
+.particle,
+.particle:after {
+ background: transparent;
+}
+
+.particle:after {
+ position: absolute;
+ content: "";
+ top: $spacing;
+}
+
+.particle-1 {
+ animation: animParticle $time-1 linear infinite;
+ @include particles(600);
+ height: 1px;
+ width: 1px;
+}
+
+.particle-1:after {
+ @include particles(600);
+ height: 1px;
+ width: 1px;
+}
+
+.particle-2 {
+ animation: animParticle $time-2 linear infinite;
+ @include particles(200);
+ height: 2px;
+ width: 2px;
+}
+
+.particle-2:after {
+ @include particles(200);
+ height: 2px;
+ width: 2px;
+}
+
+.particle-3 {
+ animation: animParticle $time-3 linear infinite;
+ @include particles(100);
+ height: 3px;
+ width: 3px;
+}
+
+.particle-3:after {
+ @include particles(100);
+ height: 3px;
+ width: 3px;
+}
+
+.particle-4 {
+ animation: animParticle $time-4 linear infinite;
+ @include particles(400);
+ height: 1px;
+ width: 1px;
+}
+
+.particle-4:after {
+ @include particles(400);
+ height: 1px;
+ width: 1px;
+}
+
+@keyframes animParticle {
+ from { transform: translateY(0px); }
+ to { transform: translateY($spacing * -1); }
+}
diff --git a/src/scss/components/_search.scss b/src/scss/components/_search.scss
new file mode 100644
index 00000000..7a4d114b
--- /dev/null
+++ b/src/scss/components/_search.scss
@@ -0,0 +1,53 @@
+@use '../base';
+
+.search {
+ width: 100%;
+ height: fit-content;
+ flex-shrink: 0;
+ display: flex;
+ justify-content: center;
+ margin-top: 100px;
+ margin-bottom: 20px;
+ .search-wrap{
+ width: 60%;
+ height: fit-content;
+ display: flex;
+ justify-content: center;
+ gap: 20px;
+ input {
+ height: 45px;
+ padding: 10px 10px;
+ flex-grow: 1;
+ background-color: base.$color--black;
+ border: 2px solid base.$color--point;
+ box-sizing: border-box;
+ color: base.$color--white;
+ border-radius: 4px;
+ font-size: 18px;
+ &:focus {
+ outline: none;
+ }
+ }
+ select {
+ height: 45px;
+ width: 100px;
+ background-color: base.$color--black;
+ border: 2px solid base.$color--point;
+ color: base.$color--point;
+ text-align: center;
+ font-size: 18px;
+ border-radius: 4px;
+ font-family: 'Poppins', sans-serif;
+ &:focus {
+ outline: none;
+ }
+ }
+ button {
+ background-color: base.$color--primary;
+ border: none;
+ &:hover {
+ background-color: base.$color--hover;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/scss/components/_searchitem.scss b/src/scss/components/_searchitem.scss
new file mode 100644
index 00000000..4a6df88f
--- /dev/null
+++ b/src/scss/components/_searchitem.scss
@@ -0,0 +1,49 @@
+@use '../base';
+
+.movie-list {
+ width: 60%;
+ box-sizing: border-box;
+ padding: 20px;
+ border-radius: 4px;
+ background-color: base.$color--primary-10;
+ .movies {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20px;
+ .movie {
+ $width: 200px;
+ width: $width;
+ height: calc($width * 3 / 2 + 50px);
+ text-decoration: none;
+ color: base.$color--white;
+ overflow: hidden;
+ .poster-wrap {
+ width: 100%;
+ height: calc(100% - 50px);
+ overflow: hidden;
+ .poster {
+ width: 100%;
+ height: 100%;
+ background-size: cover;
+ transition: .4s;
+ &:hover {
+ transform: scale(1.1);
+ }
+ }
+ }
+ .title {
+ width: 100%;
+ height: 50px;
+ color:base.$color--white;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
+ }
+ .message {
+ color: base.$color--primary;
+ font-size: 20px;
+ text-align: center;
+ }
+}
diff --git a/src/scss/components/_smallslide.scss b/src/scss/components/_smallslide.scss
new file mode 100644
index 00000000..0119b7bc
--- /dev/null
+++ b/src/scss/components/_smallslide.scss
@@ -0,0 +1,42 @@
+@use '../base';
+
+.small-slide {
+ position: relative;
+ width: 100%;
+ height: 250px;
+ box-sizing: border-box;
+ flex-shrink: 0;
+ margin-top: 20px;
+ p {
+ margin: 0;
+ color: base.$color--point;
+ font-size: 18px;
+ margin-right: 60px;
+ font-family: 'Space Mono', monospace;
+ text-align: right;
+ }
+ swiper-container {
+ width: 100%;
+ height: 100%;
+ padding: 0 20px;
+ box-sizing: border-box;
+ swiper-slide {
+ text-align: center;
+ font-size: 18px;
+ background: #fff;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ transition: .2s;
+ &:hover {
+ transform: translateY(-5px)
+ }
+ .poster {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+ }
+}
diff --git a/src/scss/components/_topvideo.scss b/src/scss/components/_topvideo.scss
new file mode 100644
index 00000000..8f745898
--- /dev/null
+++ b/src/scss/components/_topvideo.scss
@@ -0,0 +1,66 @@
+@use '../base';
+
+.video-container {
+ position: relative;
+ width: 100%;
+ height: 80%;
+ flex-shrink: 0;
+ overflow: hidden;
+ .player {
+ width: 100%;
+ background-color: base.$color--black;
+ display: flex;
+ align-items: center;
+ video {
+ position: absolute;
+ width: 100%;
+ bottom: 0;
+ }
+ .volume {
+ position: absolute;
+ right: 10px;
+ bottom: 10px;
+ background-color: transparent;
+ border: none;
+ color: base.$color--white;
+ }
+ .info {
+ position: absolute;
+ margin-left: 60px;
+ top: 20%;
+ width: 40%;
+ height: 200px;
+ z-index: 12;
+ .title {
+ font-size: 36px;
+ font-weight: 700;
+ color: base.$color--primary;
+ font-family: 'Space Mono', monospace;
+ margin: 0;
+ }
+ .year {
+ width: fit-content;
+ margin-bottom: 10px;
+ color: base.$color--blue;
+ font-size: 14px;
+ }
+ .plot {
+ font-size: 20px;
+ color: base.$color--white;
+ }
+ .btn-more {
+ background-color: transparent;
+ border: 2px solid base.$color--primary;
+ border-radius: 8px;
+ color: base.$color--primary;
+ margin-top: 20px;
+ &:hover {
+ background-color: base.$color--primary;
+ color: base.$color--black;
+ }
+ }
+ }
+ }
+}
+
+
diff --git a/src/scss/pages/_home.scss b/src/scss/pages/_home.scss
new file mode 100644
index 00000000..bb574567
--- /dev/null
+++ b/src/scss/pages/_home.scss
@@ -0,0 +1,17 @@
+@use '../base';
+
+body.scroll-hidden {
+ overflow: hidden;
+}
+
+.page-home {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+}
\ No newline at end of file
diff --git a/src/scss/pages/_index.scss b/src/scss/pages/_index.scss
new file mode 100644
index 00000000..76c6e69d
--- /dev/null
+++ b/src/scss/pages/_index.scss
@@ -0,0 +1,3 @@
+@forward 'start';
+@forward 'home';
+@forward 'notfound';
diff --git a/src/scss/pages/_notfound.scss b/src/scss/pages/_notfound.scss
new file mode 100644
index 00000000..6ba587d0
--- /dev/null
+++ b/src/scss/pages/_notfound.scss
@@ -0,0 +1,19 @@
+@use '../base';
+
+.page-notfound {
+ display: flex;
+ justify-content: center;
+ .msg {
+ margin-top: 50%;
+ position: relative;
+ font-size: 70px;
+ font-weight: 700;
+ color: base.$color--primary;
+ text-align: center;
+ z-index:3px;
+ }
+ footer {
+ position: absolute;
+ bottom: 0;
+ }
+}
\ No newline at end of file
diff --git a/src/scss/pages/_start.scss b/src/scss/pages/_start.scss
new file mode 100644
index 00000000..9b7e9e07
--- /dev/null
+++ b/src/scss/pages/_start.scss
@@ -0,0 +1,93 @@
+@use '../base';
+
+.page-start {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ .headline {
+ position: relative;
+ width: 60%;
+ margin-top: 60px;
+ color: base.$color--primary;
+ z-index: 3;
+ .title {
+ font-family: 'Poppins', sans-serif;
+ color: base.$color--primary;
+ span {
+ text-decoration: underline;
+ }
+ }
+ .text {
+ color: base.$color--primary;
+ font-size: 20px;
+ font-family: 'Space Mono', monospace;
+ }
+ }
+ .selector-genre {
+ position: relative;
+ background-color: transparent;
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+ z-index: 3;
+ margin-top: 20px;
+ .text {
+ color: base.$color--white;
+ }
+ .btn-group {
+ width: 70%;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-content: flex-start;
+ margin: 1rem;
+ input[type="radio"] {
+ position: absolute;
+ opacity: 0;
+ }
+ .btn-selector {
+ height: 24px;
+ display: block;
+ padding: 10px 20px;
+ margin: 0.25rem;
+ background-color: transparent;
+ border: 2px solid base.$color--point;
+ border-radius: 24px;
+ text-align: center;
+ color: base.$color--white;
+ cursor: pointer;
+ &:hover {
+ color: base.$color--point;
+ }
+ }
+ input[type="radio"]:checked + .btn-selector {
+ background-color: base.$color--point;
+ color: base.$color--white;
+ }
+ }
+ }
+
+ .btn-gosearch {
+ position: relative;
+ width: 220px;
+ height: 45px;
+ margin-top: 50px;
+ padding: 5px 0;
+ background-color: base.$color--blue;
+ border-radius: 4px;
+ cursor: pointer;
+ outline: none;
+ z-index: 9;
+ font-size: 20px;
+ text-decoration: none;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: base.$color--black;
+ font-family: 'Poppins', sans-serif;
+ &:hover {
+ color: #fff;
+ }
+ }
+}
diff --git a/src/scss/style.scss b/src/scss/style.scss
new file mode 100644
index 00000000..da73d0ff
--- /dev/null
+++ b/src/scss/style.scss
@@ -0,0 +1,3 @@
+@use 'base';
+@use 'pages';
+@use 'components';
diff --git a/src/store/about.js b/src/store/about.js
new file mode 100644
index 00000000..07064acc
--- /dev/null
+++ b/src/store/about.js
@@ -0,0 +1,7 @@
+import { Store } from '../core/core'
+
+export default new Store({
+ name: '7581058',
+ github: 'https://github.com/7581058',
+ repository: 'https://github.com/7581058/omdb-app'
+})
diff --git a/src/store/movie.js b/src/store/movie.js
new file mode 100644
index 00000000..54c3b10a
--- /dev/null
+++ b/src/store/movie.js
@@ -0,0 +1,56 @@
+import { Store } from '../core/core'
+
+const store = new Store({
+ searchText: '',
+ searchYear: '',
+ page: 1,
+ pageMax: 1,
+ movies: [],
+ movie: {},
+ message: 'Search for the movie title',
+ listLoading: false,
+ modal: false,
+ contents: false,
+ movieID: '',
+ muted: true
+})
+
+export default store
+
+export const searchMovies = async page => {
+ store.state.listLoading = true
+ store.state.page = page
+ if (page === 1) {
+ store.state.movies = []
+ store.state.message = ''
+ }
+ try {
+ const res = await fetch(
+ `https://omdbapi.com?apikey=14c167f8&s=${store.state.searchText}&y=${store.state.searchYear}&page=${store.state.page}`
+ )
+ const { Search, totalResults, Response, Error } = await res.json()
+ if (Response === 'True') {
+ store.state.movies = [...store.state.movies, ...Search]
+ store.state.pageMax = Math.ceil(Number(totalResults) / 10)
+ } else {
+ store.state.message = Error
+ store.state.pageMax = 1
+ }
+ } catch (error) {
+ console.log('searchMovies error:', error)
+ } finally {
+ store.state.listLoading = false
+ }
+}
+
+export const getMovieDetails = async id => {
+ try {
+ const res = await fetch(
+ `https://omdbapi.com?apikey=14c167f8&i=${id}&plot=full`
+ )
+ store.state.movie = await res.json()
+ store.state.contents = true
+ } catch (error) {
+ console.log('getMovieDetails error:', error)
+ }
+}
diff --git a/src/store/recommend.js b/src/store/recommend.js
new file mode 100644
index 00000000..706adab3
--- /dev/null
+++ b/src/store/recommend.js
@@ -0,0 +1,164 @@
+import { Store } from '../core/core'
+
+const store = new Store({
+ newmovies: [
+ {
+ title: 'μ μ΄',
+ id: 'tt22352848',
+ duration: 91000
+ },
+ {
+ title: 'μ€μμΉ',
+ id: 'tt24076346',
+ duration: 64000
+ },
+ {
+ title: 'κ΅μ',
+ id: 'tt21111120',
+ duration: 57000
+ }
+ ],
+ genres: [
+ {
+ name: 'No Thanks',
+ code: 'genre_00'
+ },
+ {
+ name: 'Action',
+ code: 'genre_01'
+ },
+ {
+ name: 'Animation',
+ code: 'genre_02'
+ },
+ {
+ name: 'Drama',
+ code: 'genre_03'
+ },
+ {
+ name: 'Science Fiction',
+ code: 'genre_04'
+ },
+ {
+ name: 'Romance',
+ code: 'genre_05'
+ }
+ ],
+ genreName: '',
+ genreArr: [],
+ pick: [
+ 'tt0265086', //λΈλνΈν¬λ€μ΄
+ 'tt2713180', //ν¨λ¦¬
+ 'tt6924650', //λ―Έλμ¨μ΄
+ 'tt8579674', //1917
+ 'tt1631867', //μ£μ§μ€λΈν¬λͺ¨λ‘μ°
+ 'tt3659388', //λ§μ
+ 'tt1136608', //λμ€νΈλ¦νΈ9
+ 'tt2239822', //λ°λ 리μ:μ²κ°νμ±μλμ
+ 'tt5580390', //λμ
°μ
μ€λΈμν°,
+ 'tt16360006', //λ²λΈ
+ 'tt0347618', //κ³ μμ΄μ보μ
+ 'tt0347149' //νμΈμμμ§μ΄λμ±
+ ],
+ actions: [
+ 'tt0265086', //λΈλνΈν¬λ€μ΄
+ 'tt2713180', //ν¨λ¦¬
+ 'tt6924650', //λ―Έλμ¨μ΄
+ 'tt8579674', //1917
+ 'tt6334354', //μμ΄μ¬μ΄νΈμ€μΏΌλ
+ 'tt2802144', //νΉμ€λ§¨
+ 'tt1905041', //λΆλ
Έμμ§μ£Ό6
+ 'tt1631867', //μ£μ§μ€λΈν¬λͺ¨λ‘μ°
+ 'tt1375666', //μΈμ
μ
+ 'tt0816711', //μλμz
+ 'tt2911666', //μ‘΄μ
+ 'tt1392190' //λ§€λλ§₯μ€
+ ],
+ drame: [
+ 'tt0923714', //ν΄λ°λΌκΈ°
+ 'tt22872370', //λκ°
+ 'tt10276054', //νκ±°
+ 'tt12663772', //μ΄μνλλΌμμνμ
+ 'tt1937339', //μ¨λ
+ 'tt9602258', //λ§λͺ¨μ΄
+ 'tt5799928', //λμ£Ό
+ 'tt6878038', //νμμ΄μ μ¬
+ 'tt6914542', //λ§μ½μ
+ 'tt14371900', //μμ°μ΄λ³΄
+ 'tt6434022', //λΉμ κ±°κΈ°μμ΄μ€λμ
+ 'tt3153634' //μμ
+ ],
+ sf: [
+ 'tt0816692', //μΈν°μ€ν
λΌ
+ 'tt3659388', //λ§μ
+ 'tt1454468', //κ·ΈλλΉν°
+ 'tt1798709', //κ·Έλ
(her)
+ 'tt1136608', //λμ€νΈλ¦νΈ9
+ 'tt1160419', //λ
+ 'tt1677720', //λ λνλ μ΄μ΄μ
+ 'tt2239822', //λ°λ 리μ:μ²κ°νμ±μλμ
+ 'tt1375666', //μΈμ
μ
+ 'tt2543164', //컨ν
νΈ
+ 'tt0133093', //λ§€νΈλ¦μ€
+ 'tt0470752' //μμ€λ§ν€λ
+ ],
+ romance: [
+ 'tt5580390', //λμ
°μ
μ€λΈμν°,
+ 'tt2337981', //건μΆνκ°λ‘
+ 'tt16303830', //μ°μ λΉ μ§λ‘맨μ€
+ 'tt12477480', //ν€μ΄μ§κ²°μ¬
+ 'tt0293715', //μ½κΈ°μ μΈκ·Έλ
+ 'tt2194499', //μ΄λ°μνμ
+ 'tt3783958', //λΌλΌλλ
+ 'tt4273886', //λ·°ν°μΈμ¬μ΄λ
+ 'tt2674426', //λ―ΈλΉν¬μ
+ 'tt1638002', //λ¬λΈλ‘μ§
+ 'tt2582846', //μλ
ν€μ΄μ¦
+ 'tt1980929' //λΉκΈ΄μ΄κ²μΈ
+ ],
+ animation: [
+ 'tt16428256', //μ€μ¦λ©μλ¬Έλ¨μ
+ 'tt11032374', //κ·μΉΌ
+ 'tt0347149', //νμΈμμμ§μ΄λμ±
+ 'tt5311514', //λμμ΄λ¦μ
+ 'tt0347618', //κ³ μμ΄μ보μ
+ 'tt1623674', //짱ꡬλλͺ»λ§λ €
+ 'tt0808506', //μκ°μλ¬λ¦¬λμλ
+ 'tt9426210', //λ μ¨μμμ΄
+ 'tt16360006', //λ²λΈ
+ 'tt2591814', //μΈμ΄μμ μ
+ 'tt5323662', //λͺ©μ리μνν
+ 'tt15494038' //νλ₯λ¨μ§
+ ]
+})
+
+export default store
+
+export const getGenre = selectedgenre => {
+ switch (selectedgenre) {
+ case 'genre_00':
+ store.state.genreName = ''
+ store.state.genreArr = store.state.pick
+ break
+ case 'genre_01':
+ store.state.genreName = 'Action'
+ store.state.genreArr = store.state.actions
+ break
+ case 'genre_02':
+ store.state.genreName = 'Animation'
+ store.state.genreArr = store.state.animation
+ break
+ case 'genre_03':
+ store.state.genreName = 'Drama'
+ store.state.genreArr = store.state.drame
+ break
+ case 'genre_04':
+ store.state.genreName = 'SF'
+ store.state.genreArr = store.state.sf
+ break
+ case 'genre_05':
+ store.state.genreName = 'Romance'
+ store.state.genreArr = store.state.romance
+ break
+ }
+}
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 00000000..08be80e3
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,45 @@
+const path = require('path')
+const HtmlPlugin = require('html-webpack-plugin')
+const CopyPlugin = require('copy-webpack-plugin')
+
+module.exports = {
+ resolve: {
+ extensions: ['.js', '.scss'],
+ alias: {
+ '~': path.resolve(__dirname, 'src')
+ }
+ },
+ entry: './src/main.js',
+ output: {
+ clean: true
+ },
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ use: 'babel-loader'
+ },
+ {
+ test: /\.s?css$/,
+ use: ['style-loader', 'css-loader', 'sass-loader']
+ },
+ {
+ test: /\.(png|jpe?g|svg|webp|gif)$/,
+ type: 'assets/resource'
+ }
+ ]
+ },
+ plugins: [
+ new HtmlPlugin({
+ template: './index.html'
+ }),
+ new CopyPlugin({
+ patterns: [
+ {
+ from: 'assets'
+ }
+ ]
+ })
+ ]
+}