diff --git a/.eslintrc b/.eslintrc index fa2cf7f0a..9b03fcda7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -12,6 +12,9 @@ "no-promise-executor-return": "off", "consistent-return": "off", "no-return-await": "off", + "no-plusplus": "off", + "camelcase": "off", + "object-curly-newline": "off", }, "env": { "es6": true, diff --git a/.gitignore b/.gitignore index c7164714d..1f8de325b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ node_modules -dist .env cypress.env.json \ No newline at end of file diff --git a/deploy/images/modal_close_button.png b/deploy/images/modal_close_button.png new file mode 100644 index 000000000..cbe1b680f Binary files /dev/null and b/deploy/images/modal_close_button.png differ diff --git a/dist/06f0f15cfcb8d681b62c.png b/dist/06f0f15cfcb8d681b62c.png new file mode 100644 index 000000000..a181ac87a Binary files /dev/null and b/dist/06f0f15cfcb8d681b62c.png differ diff --git a/dist/2e162b4fefb34cd7ed8d.png b/dist/2e162b4fefb34cd7ed8d.png new file mode 100644 index 000000000..b2a61da8e Binary files /dev/null and b/dist/2e162b4fefb34cd7ed8d.png differ diff --git a/dist/6328741810b732410eec.png b/dist/6328741810b732410eec.png new file mode 100644 index 000000000..a17bed7c6 Binary files /dev/null and b/dist/6328741810b732410eec.png differ diff --git a/dist/6c9611deedf4b85849c9.png b/dist/6c9611deedf4b85849c9.png new file mode 100644 index 000000000..241de4f2d Binary files /dev/null and b/dist/6c9611deedf4b85849c9.png differ diff --git a/dist/bundle.js b/dist/bundle.js new file mode 100644 index 000000000..cbebee4ac --- /dev/null +++ b/dist/bundle.js @@ -0,0 +1,484 @@ +/* + * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). + * This devtool is neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). + */ +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ "./node_modules/css-loader/dist/cjs.js!./src/css/common.css": +/*!******************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js!./src/css/common.css ***! + \******************************************************************/ +/***/ ((module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/getUrl.js */ \"./node_modules/css-loader/dist/runtime/getUrl.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2__);\n// Imports\n\n\n\nvar ___CSS_LOADER_URL_IMPORT_0___ = new URL(/* asset import */ __webpack_require__(/*! ../images/search_button.png */ \"./src/images/search_button.png\"), __webpack_require__.b);\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\nvar ___CSS_LOADER_URL_REPLACEMENT_0___ = _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2___default()(___CSS_LOADER_URL_IMPORT_0___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"* {\\r\\n box-sizing: border-box !important;\\r\\n}\\r\\n\\r\\nbody {\\r\\n position: relative;\\r\\n\\r\\n background-color: var(--black);\\r\\n\\r\\n font-family: Roboto;\\r\\n font-size: 14px;\\r\\n color: var(--white);\\r\\n}\\r\\n\\r\\na {\\r\\n color: inherit;\\r\\n text-decoration: none;\\r\\n}\\r\\n\\r\\nbutton {\\r\\n cursor: pointer;\\r\\n}\\r\\n\\r\\n#app {\\r\\n margin-top: 150px;\\r\\n padding-bottom: 48px;\\r\\n}\\r\\n\\r\\n*:focus {\\r\\n outline: none;\\r\\n}\\r\\n.item-view,\\r\\n.item-test {\\r\\n width: 100%;\\r\\n}\\r\\n\\r\\n.item-view {\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n justify-content: center;\\r\\n max-width: 1200px;\\r\\n margin: 0 auto;\\r\\n}\\r\\n\\r\\n.item-view h2 {\\r\\n font-size: 2rem;\\r\\n font-weight: bold;\\r\\n user-select: none;\\r\\n}\\r\\n\\r\\n/* https://andrew.hedges.name/experiments/aspect_ratio/ */\\r\\n\\r\\n.item-list {\\r\\n display: grid;\\r\\n margin: 48px 0;\\r\\n grid-template-columns: repeat(4, 180px);\\r\\n grid-column-gap: 160px;\\r\\n grid-row-gap: 48px;\\r\\n}\\r\\n\\r\\n.item-card {\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n}\\r\\n\\r\\n.item-thumbnail {\\r\\n border-radius: 8px;\\r\\n width: 180px;\\r\\n height: 270px;\\r\\n background-size: contain;\\r\\n}\\r\\n\\r\\n.item-title {\\r\\n margin-top: 16px;\\r\\n font-size: 1.2rem;\\r\\n font-weight: bold;\\r\\n}\\r\\n\\r\\n.item-score {\\r\\n margin-top: 16px;\\r\\n font-size: 1.2rem;\\r\\n display: inline-block;\\r\\n vertical-align: middle;\\r\\n}\\r\\n\\r\\n.item-score::after {\\r\\n margin-left: 8px;\\r\\n}\\r\\n\\r\\n.item-title.skeleton::after,\\r\\n.item-score.skeleton::after {\\r\\n font-size: 0;\\r\\n content: 'loading';\\r\\n}\\r\\n\\r\\n.full-width {\\r\\n width: 100%;\\r\\n}\\r\\n\\r\\n.last-item {\\r\\n margin-top: 48px;\\r\\n}\\r\\n\\r\\nbutton.btn {\\r\\n border: 0;\\r\\n border-radius: 8px;\\r\\n height: 30px;\\r\\n color: var(--white);\\r\\n}\\r\\n\\r\\nbutton.primary {\\r\\n background: var(--red);\\r\\n}\\r\\n\\r\\n.item-card .skeleton {\\r\\n background: linear-gradient(-90deg, var(--gray), var(--light-gray), var(--gray), var(--light-gray));\\r\\n background-size: 400%;\\r\\n animation: skeleton-animation 5s infinite ease-out;\\r\\n border-radius: 8px;\\r\\n}\\r\\n\\r\\n@keyframes skeleton-animation {\\r\\n 0% {\\r\\n background-position: 0% 50%;\\r\\n }\\r\\n 50% {\\r\\n background-position: 100% 50%;\\r\\n }\\r\\n 100% {\\r\\n background-position: 0% 50%;\\r\\n }\\r\\n}\\r\\n\\r\\nheader {\\r\\n width: 100%;\\r\\n min-width: 1200px;\\r\\n height: 72px;\\r\\n background-color: var(--black);\\r\\n display: flex;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n padding: 0 20px;\\r\\n border-bottom: 1px solid var(--white);\\r\\n margin-bottom: 48px;\\r\\n\\r\\n position: fixed;\\r\\n top: 0;\\r\\n left: 0;\\r\\n z-index: 10;\\r\\n}\\r\\n\\r\\nheader h1 {\\r\\n cursor: pointer;\\r\\n user-select: none;\\r\\n font-size: 2rem;\\r\\n font-weight: bold;\\r\\n letter-spacing: -0.1rem;\\r\\n color: var(--red);\\r\\n}\\r\\n\\r\\n#home-button {\\r\\n background-color: transparent;\\r\\n border: none;\\r\\n}\\r\\n\\r\\nheader > .search-box {\\r\\n background: var(--white);\\r\\n padding: 8px;\\r\\n border-radius: 4px;\\r\\n}\\r\\n\\r\\nheader .search-box > input {\\r\\n border: 0;\\r\\n}\\r\\n\\r\\nheader .search-box > .search-button {\\r\\n width: 14px;\\r\\n border: 0;\\r\\n text-indent: -1000rem;\\r\\n background: url(\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \") transparent no-repeat 0 1px;\\r\\n background-size: contain;\\r\\n}\\r\\n\\r\\n@media only screen and (max-width: 834px) {\\r\\n .item-view {\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n justify-content: center;\\r\\n max-width: 674px;\\r\\n margin: 0 auto;\\r\\n }\\r\\n\\r\\n .item-list {\\r\\n display: grid;\\r\\n margin: 36px 0;\\r\\n grid-template-columns: repeat(3, 180px);\\r\\n grid-column-gap: 64px;\\r\\n grid-row-gap: 55px;\\r\\n }\\r\\n\\r\\n header {\\r\\n min-width: 674px;\\r\\n }\\r\\n}\\r\\n\\r\\n@media only screen and (max-width: 390px) {\\r\\n .item-view {\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n justify-content: center;\\r\\n max-width: 316px;\\r\\n margin: 0 auto;\\r\\n }\\r\\n\\r\\n .item-list {\\r\\n display: grid;\\r\\n margin: 24px 0;\\r\\n grid-template-columns: repeat(2, 140px);\\r\\n grid-column-gap: 36px;\\r\\n grid-row-gap: 32px;\\r\\n }\\r\\n\\r\\n .item-thumbnail {\\r\\n border-radius: 15px;\\r\\n width: 140px;\\r\\n height: 220px;\\r\\n background-size: contain;\\r\\n }\\r\\n}\\r\\n\", \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/css/common.css?./node_modules/css-loader/dist/cjs.js"); + +/***/ }), + +/***/ "./node_modules/css-loader/dist/cjs.js!./src/css/modal.css": +/*!*****************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js!./src/css/modal.css ***! + \*****************************************************************/ +/***/ ((module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/getUrl.js */ \"./node_modules/css-loader/dist/runtime/getUrl.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2__);\n// Imports\n\n\n\nvar ___CSS_LOADER_URL_IMPORT_0___ = new URL(/* asset import */ __webpack_require__(/*! ../images/modal_close_button.png */ \"./src/images/modal_close_button.png\"), __webpack_require__.b);\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\nvar ___CSS_LOADER_URL_REPLACEMENT_0___ = _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2___default()(___CSS_LOADER_URL_IMPORT_0___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \".modal {\\r\\n display: none;\\r\\n position: absolute;\\r\\n top: 0;\\r\\n left: 0;\\r\\n}\\r\\n\\r\\n.modal--open {\\r\\n display: block;\\r\\n}\\r\\n\\r\\n.modal-backdrop {\\r\\n position: fixed;\\r\\n top: 0;\\r\\n right: 0;\\r\\n bottom: 0;\\r\\n left: 0;\\r\\n\\r\\n background: rgba(0, 0, 0, 0.6);\\r\\n\\r\\n z-index: 100;\\r\\n}\\r\\n\\r\\n.modal-container {\\r\\n position: fixed;\\r\\n top: 50%;\\r\\n left: 50%;\\r\\n transform: translate(-50%, -50%);\\r\\n gap: 0px;\\r\\n width: 740px;\\r\\n height: 544px;\\r\\n\\r\\n border-radius: 8px;\\r\\n background-color: rgba(33, 33, 34, 1);\\r\\n\\r\\n z-index: 200;\\r\\n}\\r\\n\\r\\n.modal-header {\\r\\n display: flex;\\r\\n justify-content: center;\\r\\n align-items: center;\\r\\n width: 740px;\\r\\n height: 60px;\\r\\n\\r\\n border-bottom: 1px solid rgba(241, 241, 241, 0.25);\\r\\n}\\r\\n\\r\\n.detail-title {\\r\\n top: 18px;\\r\\n left: 32px;\\r\\n gap: 0px;\\r\\n\\r\\n width: 676px;\\r\\n height: 24px;\\r\\n}\\r\\n\\r\\n.modal-close-button {\\r\\n width: 36px;\\r\\n height: 36px;\\r\\n\\r\\n border: none;\\r\\n background-color: transparent;\\r\\n background-image: url(\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \");\\r\\n}\\r\\n\\r\\n.modal-body {\\r\\n display: flex;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n padding: 40px 32px;\\r\\n gap: 32px;\\r\\n\\r\\n width: 740px;\\r\\n height: 484px;\\r\\n}\\r\\n\\r\\n.detail-poster {\\r\\n position: fixed;\\r\\n top: 96px;\\r\\n left: 32px;\\r\\n gap: 0px;\\r\\n\\r\\n width: 260px;\\r\\n height: 400px;\\r\\n}\\r\\n\\r\\n.modal-contents {\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n position: fixed;\\r\\n top: 96px;\\r\\n left: 324px;\\r\\n\\r\\n width: 385px;\\r\\n height: 400px;\\r\\n}\\r\\n\\r\\n.detail-text-container {\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n gap: 16px;\\r\\n margin-bottom: auto;\\r\\n margin-top: 0;\\r\\n}\\r\\n\\r\\n.detail-text-top {\\r\\n display: flex;\\r\\n align-items: flex-start;\\r\\n gap: 16px;\\r\\n\\r\\n width: 100%;\\r\\n}\\r\\n\\r\\n.detail-genres {\\r\\n}\\r\\n\\r\\n.detail-vote_average {\\r\\n}\\r\\n\\r\\n.detail-overview {\\r\\n max-height: 280px;\\r\\n overflow-y: auto;\\r\\n}\\r\\n\\r\\n.my-vote {\\r\\n display: flex;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n padding: 0 18px;\\r\\n margin-top: auto;\\r\\n margin-bottom: 0;\\r\\n\\r\\n width: 384px;\\r\\n height: 64px;\\r\\n\\r\\n border-radius: 16px;\\r\\n background-color: rgba(56, 56, 57, 1);\\r\\n}\\r\\n\\r\\n.my-vote-title {\\r\\n margin-right: 10px;\\r\\n}\\r\\n\\r\\n.my-vote-body {\\r\\n display: flex;\\r\\n gap: 0;\\r\\n margin-right: 10px;\\r\\n\\r\\n width: 160px;\\r\\n}\\r\\n\\r\\n.my-vote-body button {\\r\\n padding: 0;\\r\\n\\r\\n border: none;\\r\\n background-color: transparent;\\r\\n}\\r\\n\\r\\n.my-vote-body img {\\r\\n width: 32px;\\r\\n height: 32px;\\r\\n}\\r\\n\\r\\n.my-vote-number {\\r\\n margin-right: 10px;\\r\\n}\\r\\n\\r\\n.my-vote-description {\\r\\n}\\r\\n\\r\\n@media only screen and (max-width: 390px) {\\r\\n .modal-container {\\r\\n position: fixed;\\r\\n top: auto;\\r\\n bottom: 0;\\r\\n left: 50%;\\r\\n transform: translateX(-50%);\\r\\n gap: 0px;\\r\\n width: 390px;\\r\\n height: 485px;\\r\\n border-radius: 8px;\\r\\n background-color: rgba(33, 33, 34, 1);\\r\\n z-index: 200;\\r\\n }\\r\\n\\r\\n .modal-header {\\r\\n display: flex;\\r\\n justify-content: center;\\r\\n align-items: center;\\r\\n width: 390px;\\r\\n height: 60px;\\r\\n\\r\\n border-bottom: 1px solid rgba(241, 241, 241, 0.25);\\r\\n }\\r\\n\\r\\n .detail-title {\\r\\n top: 18px;\\r\\n left: 32px;\\r\\n gap: 0px;\\r\\n\\r\\n width: 330px;\\r\\n height: 24px;\\r\\n }\\r\\n\\r\\n .modal-close-button {\\r\\n width: 36px;\\r\\n height: 36px;\\r\\n\\r\\n border: none;\\r\\n background-color: transparent;\\r\\n background-image: url(\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \");\\r\\n }\\r\\n\\r\\n .modal-body {\\r\\n display: flex;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n padding: 40px 32px;\\r\\n gap: 0px;\\r\\n\\r\\n width: 326px;\\r\\n height: 232px;\\r\\n }\\r\\n\\r\\n .detail-poster {\\r\\n display: none;\\r\\n width: 0px;\\r\\n height: 0px;\\r\\n }\\r\\n\\r\\n .modal-contents {\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n margin-bottom: 36px;\\r\\n position: fixed;\\r\\n top: 92px;\\r\\n left: 32px;\\r\\n\\r\\n width: 326px;\\r\\n height: 232px;\\r\\n }\\r\\n\\r\\n .detail-text-container {\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n gap: 16px;\\r\\n margin-bottom: 16px;\\r\\n margin-top: 0;\\r\\n }\\r\\n\\r\\n .detail-text-top {\\r\\n display: flex;\\r\\n align-items: flex-start;\\r\\n gap: 16px;\\r\\n\\r\\n width: 100%;\\r\\n }\\r\\n\\r\\n .detail-genres {\\r\\n }\\r\\n\\r\\n .detail-vote_average {\\r\\n }\\r\\n\\r\\n .detail-overview {\\r\\n max-height: 240px;\\r\\n overflow-y: auto;\\r\\n }\\r\\n\\r\\n .my-vote {\\r\\n display: flex;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n padding: 0 18px;\\r\\n margin-top: auto;\\r\\n margin-bottom: 0;\\r\\n\\r\\n width: 326px;\\r\\n height: 64px;\\r\\n\\r\\n border-radius: 12px;\\r\\n background-color: rgba(56, 56, 57, 1);\\r\\n }\\r\\n\\r\\n .my-vote-title {\\r\\n margin-right: 10px;\\r\\n }\\r\\n\\r\\n .my-vote-body {\\r\\n display: flex;\\r\\n gap: 0;\\r\\n margin-right: 10px;\\r\\n\\r\\n width: 160px;\\r\\n }\\r\\n\\r\\n .my-vote-body button {\\r\\n padding: 0;\\r\\n\\r\\n border: none;\\r\\n background-color: transparent;\\r\\n }\\r\\n\\r\\n .my-vote-body img {\\r\\n width: 32px;\\r\\n height: 32px;\\r\\n }\\r\\n\\r\\n .my-vote-number {\\r\\n margin-right: 10px;\\r\\n }\\r\\n\\r\\n .my-vote-description {\\r\\n display: none;\\r\\n }\\r\\n}\\r\\n\", \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/css/modal.css?./node_modules/css-loader/dist/cjs.js"); + +/***/ }), + +/***/ "./node_modules/css-loader/dist/cjs.js!./src/css/reset.css": +/*!*****************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js!./src/css/reset.css ***! + \*****************************************************************/ +/***/ ((module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"/* http://meyerweb.com/eric/tools/css/reset/\\r\\n v2.0 | 20110126\\r\\n License: none (public domain)\\r\\n*/\\r\\n\\r\\nhtml,\\r\\nbody,\\r\\ndiv,\\r\\nspan,\\r\\napplet,\\r\\nobject,\\r\\niframe,\\r\\nh1,\\r\\nh2,\\r\\nh3,\\r\\nh4,\\r\\nh5,\\r\\nh6,\\r\\np,\\r\\nblockquote,\\r\\npre,\\r\\na,\\r\\nabbr,\\r\\nacronym,\\r\\naddress,\\r\\nbig,\\r\\ncite,\\r\\ncode,\\r\\ndel,\\r\\ndfn,\\r\\nem,\\r\\nimg,\\r\\nins,\\r\\nkbd,\\r\\nq,\\r\\ns,\\r\\nsamp,\\r\\nsmall,\\r\\nstrike,\\r\\nstrong,\\r\\nsub,\\r\\nsup,\\r\\ntt,\\r\\nvar,\\r\\nb,\\r\\nu,\\r\\ni,\\r\\ncenter,\\r\\ndl,\\r\\ndt,\\r\\ndd,\\r\\nol,\\r\\nul,\\r\\nli,\\r\\nfieldset,\\r\\nform,\\r\\nlabel,\\r\\nlegend,\\r\\ntable,\\r\\ncaption,\\r\\ntbody,\\r\\ntfoot,\\r\\nthead,\\r\\ntr,\\r\\nth,\\r\\ntd,\\r\\narticle,\\r\\naside,\\r\\ncanvas,\\r\\ndetails,\\r\\nembed,\\r\\nfigure,\\r\\nfigcaption,\\r\\nfooter,\\r\\nheader,\\r\\nhgroup,\\r\\nmenu,\\r\\nnav,\\r\\noutput,\\r\\nruby,\\r\\nsection,\\r\\nsummary,\\r\\ntime,\\r\\nmark,\\r\\naudio,\\r\\nvideo {\\r\\n margin: 0;\\r\\n padding: 0;\\r\\n border: 0;\\r\\n font-size: 100%;\\r\\n font: inherit;\\r\\n vertical-align: baseline;\\r\\n}\\r\\n/* HTML5 display-role reset for older browsers */\\r\\narticle,\\r\\naside,\\r\\ndetails,\\r\\nfigcaption,\\r\\nfigure,\\r\\nfooter,\\r\\nheader,\\r\\nhgroup,\\r\\nmenu,\\r\\nnav,\\r\\nsection {\\r\\n display: block;\\r\\n}\\r\\nbody {\\r\\n line-height: 1;\\r\\n}\\r\\nol,\\r\\nul {\\r\\n list-style: none;\\r\\n}\\r\\nblockquote,\\r\\nq {\\r\\n quotes: none;\\r\\n}\\r\\nblockquote:before,\\r\\nblockquote:after,\\r\\nq:before,\\r\\nq:after {\\r\\n content: '';\\r\\n content: none;\\r\\n}\\r\\ntable {\\r\\n border-collapse: collapse;\\r\\n border-spacing: 0;\\r\\n}\\r\\n\", \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/css/reset.css?./node_modules/css-loader/dist/cjs.js"); + +/***/ }), + +/***/ "./node_modules/css-loader/dist/cjs.js!./src/css/theme.css": +/*!*****************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js!./src/css/theme.css ***! + \*****************************************************************/ +/***/ ((module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"/* Color ******************************************/\\r\\n:root {\\r\\n --white: #fff;\\r\\n --light-gray: #f0f0f0;\\r\\n --gray: #aaa;\\r\\n --black: #222;\\r\\n --red: #f33f3f;\\r\\n}\\r\\n\\r\\n/* Typography *************************************/\\r\\n.text-detail-title {\\r\\n font-size: 20px;\\r\\n font-weight: 600;\\r\\n line-height: 24px;\\r\\n letter-spacing: 0.15px;\\r\\n text-align: center;\\r\\n}\\r\\n\\r\\n.text-detail-contents {\\r\\n font-size: 16px;\\r\\n font-weight: 400;\\r\\n line-height: 24px;\\r\\n letter-spacing: 0.5px;\\r\\n text-align: left;\\r\\n color: rgba(241, 241, 241, 1);\\r\\n}\\r\\n\\r\\n.text-detail-vote {\\r\\n font-size: 16px;\\r\\n font-weight: 700;\\r\\n line-height: 24px;\\r\\n letter-spacing: 0.5px;\\r\\n text-align: left;\\r\\n}\\r\\n\\r\\n.text-detail-vote-contents {\\r\\n font-size: 16px;\\r\\n font-weight: 400;\\r\\n line-height: 24px;\\r\\n letter-spacing: 0.5px;\\r\\n text-align: center;\\r\\n}\\r\\n\", \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/css/theme.css?./node_modules/css-loader/dist/cjs.js"); + +/***/ }), + +/***/ "./node_modules/css-loader/dist/runtime/api.js": +/*!*****************************************************!*\ + !*** ./node_modules/css-loader/dist/runtime/api.js ***! + \*****************************************************/ +/***/ ((module) => { + +eval("\n\n/*\n MIT License http://www.opensource.org/licenses/mit-license.php\n Author Tobias Koppers @sokra\n*/\nmodule.exports = function (cssWithMappingToString) {\n var list = [];\n\n // return the list of modules as css string\n list.toString = function toString() {\n return this.map(function (item) {\n var content = \"\";\n var needLayer = typeof item[5] !== \"undefined\";\n if (item[4]) {\n content += \"@supports (\".concat(item[4], \") {\");\n }\n if (item[2]) {\n content += \"@media \".concat(item[2], \" {\");\n }\n if (needLayer) {\n content += \"@layer\".concat(item[5].length > 0 ? \" \".concat(item[5]) : \"\", \" {\");\n }\n content += cssWithMappingToString(item);\n if (needLayer) {\n content += \"}\";\n }\n if (item[2]) {\n content += \"}\";\n }\n if (item[4]) {\n content += \"}\";\n }\n return content;\n }).join(\"\");\n };\n\n // import a list of modules into the list\n list.i = function i(modules, media, dedupe, supports, layer) {\n if (typeof modules === \"string\") {\n modules = [[null, modules, undefined]];\n }\n var alreadyImportedModules = {};\n if (dedupe) {\n for (var k = 0; k < this.length; k++) {\n var id = this[k][0];\n if (id != null) {\n alreadyImportedModules[id] = true;\n }\n }\n }\n for (var _k = 0; _k < modules.length; _k++) {\n var item = [].concat(modules[_k]);\n if (dedupe && alreadyImportedModules[item[0]]) {\n continue;\n }\n if (typeof layer !== \"undefined\") {\n if (typeof item[5] === \"undefined\") {\n item[5] = layer;\n } else {\n item[1] = \"@layer\".concat(item[5].length > 0 ? \" \".concat(item[5]) : \"\", \" {\").concat(item[1], \"}\");\n item[5] = layer;\n }\n }\n if (media) {\n if (!item[2]) {\n item[2] = media;\n } else {\n item[1] = \"@media \".concat(item[2], \" {\").concat(item[1], \"}\");\n item[2] = media;\n }\n }\n if (supports) {\n if (!item[4]) {\n item[4] = \"\".concat(supports);\n } else {\n item[1] = \"@supports (\".concat(item[4], \") {\").concat(item[1], \"}\");\n item[4] = supports;\n }\n }\n list.push(item);\n }\n };\n return list;\n};\n\n//# sourceURL=webpack://javascript-movie-review/./node_modules/css-loader/dist/runtime/api.js?"); + +/***/ }), + +/***/ "./node_modules/css-loader/dist/runtime/getUrl.js": +/*!********************************************************!*\ + !*** ./node_modules/css-loader/dist/runtime/getUrl.js ***! + \********************************************************/ +/***/ ((module) => { + +eval("\n\nmodule.exports = function (url, options) {\n if (!options) {\n options = {};\n }\n if (!url) {\n return url;\n }\n url = String(url.__esModule ? url.default : url);\n\n // If url is already wrapped in quotes, remove them\n if (/^['\"].*['\"]$/.test(url)) {\n url = url.slice(1, -1);\n }\n if (options.hash) {\n url += options.hash;\n }\n\n // Should url be wrapped?\n // See https://drafts.csswg.org/css-values-3/#urls\n if (/[\"'() \\t\\n]|(%20)/.test(url) || options.needQuotes) {\n return \"\\\"\".concat(url.replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\"), \"\\\"\");\n }\n return url;\n};\n\n//# sourceURL=webpack://javascript-movie-review/./node_modules/css-loader/dist/runtime/getUrl.js?"); + +/***/ }), + +/***/ "./node_modules/css-loader/dist/runtime/noSourceMaps.js": +/*!**************************************************************!*\ + !*** ./node_modules/css-loader/dist/runtime/noSourceMaps.js ***! + \**************************************************************/ +/***/ ((module) => { + +eval("\n\nmodule.exports = function (i) {\n return i[1];\n};\n\n//# sourceURL=webpack://javascript-movie-review/./node_modules/css-loader/dist/runtime/noSourceMaps.js?"); + +/***/ }), + +/***/ "./src/css/common.css": +/*!****************************!*\ + !*** ./src/css/common.css ***! + \****************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleDomAPI.js */ \"./node_modules/style-loader/dist/runtime/styleDomAPI.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertBySelector.js */ \"./node_modules/style-loader/dist/runtime/insertBySelector.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js */ \"./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertStyleElement.js */ \"./node_modules/style-loader/dist/runtime/insertStyleElement.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleTagTransform.js */ \"./node_modules/style-loader/dist/runtime/styleTagTransform.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _node_modules_css_loader_dist_cjs_js_common_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../../node_modules/css-loader/dist/cjs.js!./common.css */ \"./node_modules/css-loader/dist/cjs.js!./src/css/common.css\");\n\n \n \n \n \n \n \n \n \n \n\nvar options = {};\n\noptions.styleTagTransform = (_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default());\noptions.setAttributes = (_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default());\n\n options.insert = _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default().bind(null, \"head\");\n \noptions.domAPI = (_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default());\noptions.insertStyleElement = (_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default());\n\nvar update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_common_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"], options);\n\n\n\n\n /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_common_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"] && _node_modules_css_loader_dist_cjs_js_common_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals ? _node_modules_css_loader_dist_cjs_js_common_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals : undefined);\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/css/common.css?"); + +/***/ }), + +/***/ "./src/css/modal.css": +/*!***************************!*\ + !*** ./src/css/modal.css ***! + \***************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleDomAPI.js */ \"./node_modules/style-loader/dist/runtime/styleDomAPI.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertBySelector.js */ \"./node_modules/style-loader/dist/runtime/insertBySelector.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js */ \"./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertStyleElement.js */ \"./node_modules/style-loader/dist/runtime/insertStyleElement.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleTagTransform.js */ \"./node_modules/style-loader/dist/runtime/styleTagTransform.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _node_modules_css_loader_dist_cjs_js_modal_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../../node_modules/css-loader/dist/cjs.js!./modal.css */ \"./node_modules/css-loader/dist/cjs.js!./src/css/modal.css\");\n\n \n \n \n \n \n \n \n \n \n\nvar options = {};\n\noptions.styleTagTransform = (_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default());\noptions.setAttributes = (_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default());\n\n options.insert = _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default().bind(null, \"head\");\n \noptions.domAPI = (_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default());\noptions.insertStyleElement = (_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default());\n\nvar update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_modal_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"], options);\n\n\n\n\n /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_modal_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"] && _node_modules_css_loader_dist_cjs_js_modal_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals ? _node_modules_css_loader_dist_cjs_js_modal_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals : undefined);\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/css/modal.css?"); + +/***/ }), + +/***/ "./src/css/reset.css": +/*!***************************!*\ + !*** ./src/css/reset.css ***! + \***************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleDomAPI.js */ \"./node_modules/style-loader/dist/runtime/styleDomAPI.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertBySelector.js */ \"./node_modules/style-loader/dist/runtime/insertBySelector.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js */ \"./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertStyleElement.js */ \"./node_modules/style-loader/dist/runtime/insertStyleElement.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleTagTransform.js */ \"./node_modules/style-loader/dist/runtime/styleTagTransform.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _node_modules_css_loader_dist_cjs_js_reset_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../../node_modules/css-loader/dist/cjs.js!./reset.css */ \"./node_modules/css-loader/dist/cjs.js!./src/css/reset.css\");\n\n \n \n \n \n \n \n \n \n \n\nvar options = {};\n\noptions.styleTagTransform = (_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default());\noptions.setAttributes = (_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default());\n\n options.insert = _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default().bind(null, \"head\");\n \noptions.domAPI = (_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default());\noptions.insertStyleElement = (_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default());\n\nvar update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_reset_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"], options);\n\n\n\n\n /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_reset_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"] && _node_modules_css_loader_dist_cjs_js_reset_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals ? _node_modules_css_loader_dist_cjs_js_reset_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals : undefined);\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/css/reset.css?"); + +/***/ }), + +/***/ "./src/css/theme.css": +/*!***************************!*\ + !*** ./src/css/theme.css ***! + \***************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleDomAPI.js */ \"./node_modules/style-loader/dist/runtime/styleDomAPI.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertBySelector.js */ \"./node_modules/style-loader/dist/runtime/insertBySelector.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js */ \"./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertStyleElement.js */ \"./node_modules/style-loader/dist/runtime/insertStyleElement.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleTagTransform.js */ \"./node_modules/style-loader/dist/runtime/styleTagTransform.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _node_modules_css_loader_dist_cjs_js_theme_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../../node_modules/css-loader/dist/cjs.js!./theme.css */ \"./node_modules/css-loader/dist/cjs.js!./src/css/theme.css\");\n\n \n \n \n \n \n \n \n \n \n\nvar options = {};\n\noptions.styleTagTransform = (_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default());\noptions.setAttributes = (_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default());\n\n options.insert = _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default().bind(null, \"head\");\n \noptions.domAPI = (_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default());\noptions.insertStyleElement = (_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default());\n\nvar update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_theme_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"], options);\n\n\n\n\n /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_theme_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"] && _node_modules_css_loader_dist_cjs_js_theme_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals ? _node_modules_css_loader_dist_cjs_js_theme_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals : undefined);\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/css/theme.css?"); + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js": +/*!****************************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js ***! + \****************************************************************************/ +/***/ ((module) => { + +eval("\n\nvar stylesInDOM = [];\n\nfunction getIndexByIdentifier(identifier) {\n var result = -1;\n\n for (var i = 0; i < stylesInDOM.length; i++) {\n if (stylesInDOM[i].identifier === identifier) {\n result = i;\n break;\n }\n }\n\n return result;\n}\n\nfunction modulesToDom(list, options) {\n var idCountMap = {};\n var identifiers = [];\n\n for (var i = 0; i < list.length; i++) {\n var item = list[i];\n var id = options.base ? item[0] + options.base : item[0];\n var count = idCountMap[id] || 0;\n var identifier = \"\".concat(id, \" \").concat(count);\n idCountMap[id] = count + 1;\n var indexByIdentifier = getIndexByIdentifier(identifier);\n var obj = {\n css: item[1],\n media: item[2],\n sourceMap: item[3],\n supports: item[4],\n layer: item[5]\n };\n\n if (indexByIdentifier !== -1) {\n stylesInDOM[indexByIdentifier].references++;\n stylesInDOM[indexByIdentifier].updater(obj);\n } else {\n var updater = addElementStyle(obj, options);\n options.byIndex = i;\n stylesInDOM.splice(i, 0, {\n identifier: identifier,\n updater: updater,\n references: 1\n });\n }\n\n identifiers.push(identifier);\n }\n\n return identifiers;\n}\n\nfunction addElementStyle(obj, options) {\n var api = options.domAPI(options);\n api.update(obj);\n\n var updater = function updater(newObj) {\n if (newObj) {\n if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap && newObj.supports === obj.supports && newObj.layer === obj.layer) {\n return;\n }\n\n api.update(obj = newObj);\n } else {\n api.remove();\n }\n };\n\n return updater;\n}\n\nmodule.exports = function (list, options) {\n options = options || {};\n list = list || [];\n var lastIdentifiers = modulesToDom(list, options);\n return function update(newList) {\n newList = newList || [];\n\n for (var i = 0; i < lastIdentifiers.length; i++) {\n var identifier = lastIdentifiers[i];\n var index = getIndexByIdentifier(identifier);\n stylesInDOM[index].references--;\n }\n\n var newLastIdentifiers = modulesToDom(newList, options);\n\n for (var _i = 0; _i < lastIdentifiers.length; _i++) {\n var _identifier = lastIdentifiers[_i];\n\n var _index = getIndexByIdentifier(_identifier);\n\n if (stylesInDOM[_index].references === 0) {\n stylesInDOM[_index].updater();\n\n stylesInDOM.splice(_index, 1);\n }\n }\n\n lastIdentifiers = newLastIdentifiers;\n };\n};\n\n//# sourceURL=webpack://javascript-movie-review/./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js?"); + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/insertBySelector.js": +/*!********************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/insertBySelector.js ***! + \********************************************************************/ +/***/ ((module) => { + +eval("\n\nvar memo = {};\n/* istanbul ignore next */\n\nfunction getTarget(target) {\n if (typeof memo[target] === \"undefined\") {\n var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself\n\n if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {\n try {\n // This will throw an exception if access to iframe is blocked\n // due to cross-origin restrictions\n styleTarget = styleTarget.contentDocument.head;\n } catch (e) {\n // istanbul ignore next\n styleTarget = null;\n }\n }\n\n memo[target] = styleTarget;\n }\n\n return memo[target];\n}\n/* istanbul ignore next */\n\n\nfunction insertBySelector(insert, style) {\n var target = getTarget(insert);\n\n if (!target) {\n throw new Error(\"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.\");\n }\n\n target.appendChild(style);\n}\n\nmodule.exports = insertBySelector;\n\n//# sourceURL=webpack://javascript-movie-review/./node_modules/style-loader/dist/runtime/insertBySelector.js?"); + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/insertStyleElement.js": +/*!**********************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/insertStyleElement.js ***! + \**********************************************************************/ +/***/ ((module) => { + +eval("\n\n/* istanbul ignore next */\nfunction insertStyleElement(options) {\n var element = document.createElement(\"style\");\n options.setAttributes(element, options.attributes);\n options.insert(element, options.options);\n return element;\n}\n\nmodule.exports = insertStyleElement;\n\n//# sourceURL=webpack://javascript-movie-review/./node_modules/style-loader/dist/runtime/insertStyleElement.js?"); + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js": +/*!**********************************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js ***! + \**********************************************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +eval("\n\n/* istanbul ignore next */\nfunction setAttributesWithoutAttributes(styleElement) {\n var nonce = true ? __webpack_require__.nc : 0;\n\n if (nonce) {\n styleElement.setAttribute(\"nonce\", nonce);\n }\n}\n\nmodule.exports = setAttributesWithoutAttributes;\n\n//# sourceURL=webpack://javascript-movie-review/./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js?"); + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/styleDomAPI.js": +/*!***************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/styleDomAPI.js ***! + \***************************************************************/ +/***/ ((module) => { + +eval("\n\n/* istanbul ignore next */\nfunction apply(styleElement, options, obj) {\n var css = \"\";\n\n if (obj.supports) {\n css += \"@supports (\".concat(obj.supports, \") {\");\n }\n\n if (obj.media) {\n css += \"@media \".concat(obj.media, \" {\");\n }\n\n var needLayer = typeof obj.layer !== \"undefined\";\n\n if (needLayer) {\n css += \"@layer\".concat(obj.layer.length > 0 ? \" \".concat(obj.layer) : \"\", \" {\");\n }\n\n css += obj.css;\n\n if (needLayer) {\n css += \"}\";\n }\n\n if (obj.media) {\n css += \"}\";\n }\n\n if (obj.supports) {\n css += \"}\";\n }\n\n var sourceMap = obj.sourceMap;\n\n if (sourceMap && typeof btoa !== \"undefined\") {\n css += \"\\n/*# sourceMappingURL=data:application/json;base64,\".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), \" */\");\n } // For old IE\n\n /* istanbul ignore if */\n\n\n options.styleTagTransform(css, styleElement, options.options);\n}\n\nfunction removeStyleElement(styleElement) {\n // istanbul ignore if\n if (styleElement.parentNode === null) {\n return false;\n }\n\n styleElement.parentNode.removeChild(styleElement);\n}\n/* istanbul ignore next */\n\n\nfunction domAPI(options) {\n var styleElement = options.insertStyleElement(options);\n return {\n update: function update(obj) {\n apply(styleElement, options, obj);\n },\n remove: function remove() {\n removeStyleElement(styleElement);\n }\n };\n}\n\nmodule.exports = domAPI;\n\n//# sourceURL=webpack://javascript-movie-review/./node_modules/style-loader/dist/runtime/styleDomAPI.js?"); + +/***/ }), + +/***/ "./node_modules/style-loader/dist/runtime/styleTagTransform.js": +/*!*********************************************************************!*\ + !*** ./node_modules/style-loader/dist/runtime/styleTagTransform.js ***! + \*********************************************************************/ +/***/ ((module) => { + +eval("\n\n/* istanbul ignore next */\nfunction styleTagTransform(css, styleElement) {\n if (styleElement.styleSheet) {\n styleElement.styleSheet.cssText = css;\n } else {\n while (styleElement.firstChild) {\n styleElement.removeChild(styleElement.firstChild);\n }\n\n styleElement.appendChild(document.createTextNode(css));\n }\n}\n\nmodule.exports = styleTagTransform;\n\n//# sourceURL=webpack://javascript-movie-review/./node_modules/style-loader/dist/runtime/styleTagTransform.js?"); + +/***/ }), + +/***/ "./src/App.ts": +/*!********************!*\ + !*** ./src/App.ts ***! + \********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ App)\n/* harmony export */ });\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./constants */ \"./src/constants.ts\");\n/* harmony import */ var _store_MovieStore__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./store/MovieStore */ \"./src/store/MovieStore.ts\");\n/* harmony import */ var _store_SearchMovieStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./store/SearchMovieStore */ \"./src/store/SearchMovieStore.ts\");\n/* harmony import */ var _components_SearchBox__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./components/SearchBox */ \"./src/components/SearchBox.ts\");\n/* harmony import */ var _components_MovieCard__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./components/MovieCard */ \"./src/components/MovieCard.ts\");\n/* harmony import */ var _components_Modal__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./components/Modal */ \"./src/components/Modal.ts\");\n/* harmony import */ var _images_logo_png__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./images/logo.png */ \"./src/images/logo.png\");\nvar __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n};\r\nvar __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n};\r\nvar __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n};\r\nvar _App_instances, _App_pageType, _App_observer, _App_isLoading, _App_skeletonBySize, _App_insertLogo, _App_getSkeletonCount, _App_generateMovieList, _App_generateSearchMovieList, _App_generateItemList, _App_changeTitle, _App_appendMovieCard, _App_generateSkeletonUI, _App_removeSkeletonUI, _App_setupIntersectionObserver, _App_observeSentinel, _App_handleIntersection, _App_loadMoreMovies, _App_removePreviousError, _App_generateSearchBox, _App_addHomeButtonEvent, _App_renderAllMovieList, _App_initEventListeners, _App_handleMovieCardClick;\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nclass App {\r\n constructor() {\r\n _App_instances.add(this);\r\n _App_pageType.set(this, 'popular');\r\n _App_observer.set(this, null);\r\n _App_isLoading.set(this, false);\r\n _App_skeletonBySize.set(this, _constants__WEBPACK_IMPORTED_MODULE_0__.SKELETON_UI_PC);\r\n _App_handleIntersection.set(this, (entries) => {\r\n entries.forEach((entry) => {\r\n if (entry.isIntersecting && entry.intersectionRatio >= 0.8 && !__classPrivateFieldGet(this, _App_isLoading, \"f\")) {\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_loadMoreMovies).call(this);\r\n }\r\n });\r\n });\r\n }\r\n run() {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_insertLogo).call(this);\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_generateMovieList).call(this);\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_generateSearchBox).call(this);\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_addHomeButtonEvent).call(this);\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_initEventListeners).call(this);\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_setupIntersectionObserver).call(this);\r\n });\r\n }\r\n}\r\n_App_pageType = new WeakMap(), _App_observer = new WeakMap(), _App_isLoading = new WeakMap(), _App_skeletonBySize = new WeakMap(), _App_handleIntersection = new WeakMap(), _App_instances = new WeakSet(), _App_insertLogo = function _App_insertLogo() {\r\n const homeButton = document.getElementById('home-button');\r\n const imgElement = document.createElement('img');\r\n imgElement.src = _images_logo_png__WEBPACK_IMPORTED_MODULE_6__;\r\n imgElement.alt = 'MovieList 로고';\r\n homeButton === null || homeButton === void 0 ? void 0 : homeButton.appendChild(imgElement);\r\n}, _App_getSkeletonCount = function _App_getSkeletonCount() {\r\n const width = window.innerWidth;\r\n let skeletonCount = __classPrivateFieldGet(this, _App_skeletonBySize, \"f\");\r\n if (width <= 390) {\r\n skeletonCount = _constants__WEBPACK_IMPORTED_MODULE_0__.SKELETON_UI_MOBILE;\r\n }\r\n else if (width <= 834) {\r\n skeletonCount = _constants__WEBPACK_IMPORTED_MODULE_0__.SKELETON_UI_TABLET;\r\n }\r\n return skeletonCount;\r\n}, _App_generateMovieList = function _App_generateMovieList() {\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_generateItemList).call(this, '지금 인기 있는 영화', () => __awaiter(this, void 0, void 0, function* () { return yield _store_MovieStore__WEBPACK_IMPORTED_MODULE_1__[\"default\"].getMovies(); }), _store_MovieStore__WEBPACK_IMPORTED_MODULE_1__[\"default\"]);\r\n}, _App_generateSearchMovieList = function _App_generateSearchMovieList() {\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_generateItemList).call(this, `\"${_store_SearchMovieStore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].query}\" 검색 결과`, () => __awaiter(this, void 0, void 0, function* () { return yield _store_SearchMovieStore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].searchMovies(); }), _store_SearchMovieStore__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\r\n}, _App_generateItemList = function _App_generateItemList(title, fetchData, store) {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_changeTitle).call(this, title);\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_removePreviousError).call(this);\r\n const ulElement = document.querySelector('ul.item-list');\r\n if (ulElement) {\r\n const skeletonCount = __classPrivateFieldGet(this, _App_instances, \"m\", _App_getSkeletonCount).call(this);\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_generateSkeletonUI).call(this, ulElement, skeletonCount);\r\n const newData = yield fetchData();\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_removeSkeletonUI).call(this);\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_appendMovieCard).call(this, newData, ulElement);\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_observeSentinel).call(this);\r\n }\r\n });\r\n}, _App_changeTitle = function _App_changeTitle(title) {\r\n const h2Element = document.querySelector('h2');\r\n if (h2Element) {\r\n h2Element.textContent = title;\r\n }\r\n}, _App_appendMovieCard = function _App_appendMovieCard(newData, ulElement) {\r\n newData.forEach((movieData) => {\r\n const card = new _components_MovieCard__WEBPACK_IMPORTED_MODULE_4__[\"default\"]({\r\n movie: movieData,\r\n });\r\n ulElement === null || ulElement === void 0 ? void 0 : ulElement.appendChild(card.element);\r\n });\r\n}, _App_generateSkeletonUI = function _App_generateSkeletonUI(ulElement, skeletonCount) {\r\n const fragment = new DocumentFragment();\r\n for (let i = 0; i < skeletonCount; i++) {\r\n const card = new _components_MovieCard__WEBPACK_IMPORTED_MODULE_4__[\"default\"]({\r\n classes: ['skeleton-container'],\r\n });\r\n fragment.appendChild(card.element);\r\n }\r\n ulElement === null || ulElement === void 0 ? void 0 : ulElement.appendChild(fragment);\r\n}, _App_removeSkeletonUI = function _App_removeSkeletonUI() {\r\n const skeletonElements = document.querySelectorAll('.skeleton-container');\r\n if (skeletonElements) {\r\n skeletonElements.forEach((skeletonElement) => {\r\n var _a;\r\n (_a = skeletonElement.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(skeletonElement);\r\n });\r\n }\r\n}, _App_setupIntersectionObserver = function _App_setupIntersectionObserver() {\r\n const options = {\r\n root: null,\r\n rootMargin: '0px',\r\n threshold: 0.8,\r\n };\r\n __classPrivateFieldSet(this, _App_observer, new IntersectionObserver(__classPrivateFieldGet(this, _App_handleIntersection, \"f\"), options), \"f\");\r\n const sentinel = document.createElement('li');\r\n sentinel.classList.add('sentinel');\r\n __classPrivateFieldGet(this, _App_observer, \"f\").observe(sentinel);\r\n}, _App_observeSentinel = function _App_observeSentinel() {\r\n const sentinel = document.querySelector('.sentinel');\r\n if (sentinel && __classPrivateFieldGet(this, _App_observer, \"f\")) {\r\n __classPrivateFieldGet(this, _App_observer, \"f\").observe(sentinel);\r\n }\r\n}, _App_loadMoreMovies = function _App_loadMoreMovies() {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n if (_store_SearchMovieStore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].presentPage === _store_SearchMovieStore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].totalPages)\r\n return;\r\n __classPrivateFieldSet(this, _App_isLoading, true, \"f\");\r\n if (__classPrivateFieldGet(this, _App_pageType, \"f\") === 'popular') {\r\n yield _store_MovieStore__WEBPACK_IMPORTED_MODULE_1__[\"default\"].increasePageCount();\r\n yield __classPrivateFieldGet(this, _App_instances, \"m\", _App_generateMovieList).call(this);\r\n }\r\n else {\r\n yield _store_SearchMovieStore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].increasePageCount();\r\n yield __classPrivateFieldGet(this, _App_instances, \"m\", _App_generateSearchMovieList).call(this);\r\n }\r\n __classPrivateFieldSet(this, _App_isLoading, false, \"f\");\r\n });\r\n}, _App_removePreviousError = function _App_removePreviousError() {\r\n var _a;\r\n const previousError = document.getElementById('error-page');\r\n if (previousError) {\r\n (_a = previousError.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(previousError);\r\n }\r\n}, _App_generateSearchBox = function _App_generateSearchBox() {\r\n const header = document.querySelector('header');\r\n const ulElement = document.querySelector('ul.item-list');\r\n const searchBox = new _components_SearchBox__WEBPACK_IMPORTED_MODULE_3__[\"default\"]({\r\n searchInputSubmit: (query) => {\r\n if (ulElement)\r\n ulElement.innerHTML = '';\r\n __classPrivateFieldSet(this, _App_pageType, 'search', \"f\");\r\n _store_SearchMovieStore__WEBPACK_IMPORTED_MODULE_2__[\"default\"].query = query;\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_generateSearchMovieList).call(this);\r\n },\r\n });\r\n header === null || header === void 0 ? void 0 : header.appendChild(searchBox.element);\r\n}, _App_addHomeButtonEvent = function _App_addHomeButtonEvent() {\r\n const homeButton = document.getElementById('home-button');\r\n if (homeButton) {\r\n homeButton.addEventListener('click', () => {\r\n __classPrivateFieldSet(this, _App_pageType, 'popular', \"f\");\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_changeTitle).call(this, '지금 인기 있는 영화');\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_removePreviousError).call(this);\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_renderAllMovieList).call(this);\r\n });\r\n }\r\n}, _App_renderAllMovieList = function _App_renderAllMovieList() {\r\n const movieDatas = _store_MovieStore__WEBPACK_IMPORTED_MODULE_1__[\"default\"].movies;\r\n if (movieDatas.length === 0)\r\n return;\r\n const ulElement = document.querySelector('ul.item-list');\r\n if (!ulElement)\r\n return;\r\n ulElement.innerHTML = '';\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_appendMovieCard).call(this, movieDatas, ulElement);\r\n __classPrivateFieldGet(this, _App_instances, \"m\", _App_observeSentinel).call(this);\r\n}, _App_initEventListeners = function _App_initEventListeners() {\r\n const itemList = document.querySelector('ul.item-list');\r\n if (itemList) {\r\n itemList.addEventListener('click', __classPrivateFieldGet(this, _App_instances, \"m\", _App_handleMovieCardClick).bind(this));\r\n }\r\n}, _App_handleMovieCardClick = function _App_handleMovieCardClick(event) {\r\n const clickedElement = event.target.closest('.item-card');\r\n if (clickedElement) {\r\n const movieId = Number(clickedElement.dataset.movieid);\r\n const modal = _components_Modal__WEBPACK_IMPORTED_MODULE_5__[\"default\"].getInstance(movieId);\r\n modal.openModal();\r\n }\r\n};\r\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/App.ts?"); + +/***/ }), + +/***/ "./src/components/ErrorRender.ts": +/*!***************************************!*\ + !*** ./src/components/ErrorRender.ts ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ ErrorRender)\n/* harmony export */ });\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../constants */ \"./src/constants.ts\");\nvar __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n};\r\nvar __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n};\r\nvar _ErrorRender_instances, _ErrorRender_errorPageElement, _ErrorRender_status, _ErrorRender_renderNoResult, _ErrorRender_renderClientError, _ErrorRender_renderServerError;\r\n\r\nclass ErrorRender {\r\n constructor(status) {\r\n _ErrorRender_instances.add(this);\r\n _ErrorRender_errorPageElement.set(this, document.createElement('div'));\r\n _ErrorRender_status.set(this, '');\r\n __classPrivateFieldSet(this, _ErrorRender_status, status, \"f\");\r\n __classPrivateFieldGet(this, _ErrorRender_errorPageElement, \"f\").id = 'error-page';\r\n const itemView = document.querySelector('.item-view');\r\n if (itemView) {\r\n itemView.appendChild(__classPrivateFieldGet(this, _ErrorRender_errorPageElement, \"f\"));\r\n }\r\n }\r\n renderError() {\r\n if (__classPrivateFieldGet(this, _ErrorRender_status, \"f\")[0] === _constants__WEBPACK_IMPORTED_MODULE_0__.ERROR_2XX)\r\n __classPrivateFieldGet(this, _ErrorRender_instances, \"m\", _ErrorRender_renderNoResult).call(this);\r\n if (__classPrivateFieldGet(this, _ErrorRender_status, \"f\")[0] === _constants__WEBPACK_IMPORTED_MODULE_0__.ERROR_4XX)\r\n __classPrivateFieldGet(this, _ErrorRender_instances, \"m\", _ErrorRender_renderClientError).call(this);\r\n if (__classPrivateFieldGet(this, _ErrorRender_status, \"f\")[0] === _constants__WEBPACK_IMPORTED_MODULE_0__.ERROR_5XX)\r\n __classPrivateFieldGet(this, _ErrorRender_instances, \"m\", _ErrorRender_renderServerError).call(this);\r\n }\r\n}\r\n_ErrorRender_errorPageElement = new WeakMap(), _ErrorRender_status = new WeakMap(), _ErrorRender_instances = new WeakSet(), _ErrorRender_renderNoResult = function _ErrorRender_renderNoResult() {\r\n __classPrivateFieldGet(this, _ErrorRender_errorPageElement, \"f\").innerHTML = '

검색 결과가 없습니다.

';\r\n}, _ErrorRender_renderClientError = function _ErrorRender_renderClientError() {\r\n __classPrivateFieldGet(this, _ErrorRender_errorPageElement, \"f\").innerHTML = '

클라이언트 에러

';\r\n}, _ErrorRender_renderServerError = function _ErrorRender_renderServerError() {\r\n __classPrivateFieldGet(this, _ErrorRender_errorPageElement, \"f\").innerHTML = '

서버 에러

';\r\n};\r\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/components/ErrorRender.ts?"); + +/***/ }), + +/***/ "./src/components/Modal.ts": +/*!*********************************!*\ + !*** ./src/components/Modal.ts ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Modal)\n/* harmony export */ });\n/* harmony import */ var _store_API__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../store/API */ \"./src/store/API.ts\");\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../constants */ \"./src/constants.ts\");\n/* harmony import */ var _images_no_image_png__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../images/no-image.png */ \"./src/images/no-image.png\");\n/* harmony import */ var _images_star_filled_png__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../images/star_filled.png */ \"./src/images/star_filled.png\");\n/* harmony import */ var _images_star_empty_png__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../images/star_empty.png */ \"./src/images/star_empty.png\");\nvar __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n};\r\nvar __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n};\r\nvar __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n};\r\nvar _Modal_isOpen, _Modal_modalElement, _Modal_movieId;\r\n\r\n\r\n\r\n\r\n\r\nclass Modal {\r\n constructor(movieId) {\r\n _Modal_isOpen.set(this, false);\r\n _Modal_modalElement.set(this, null);\r\n _Modal_movieId.set(this, void 0);\r\n __classPrivateFieldSet(this, _Modal_movieId, movieId, \"f\");\r\n }\r\n generateModal() {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n if (__classPrivateFieldGet(this, _Modal_modalElement, \"f\")) {\r\n __classPrivateFieldGet(this, _Modal_modalElement, \"f\").remove();\r\n __classPrivateFieldSet(this, _Modal_modalElement, null, \"f\");\r\n }\r\n const movieDetail = yield (0,_store_API__WEBPACK_IMPORTED_MODULE_0__.fetchMovieDetail)(__classPrivateFieldGet(this, _Modal_movieId, \"f\"));\r\n const posterPath = movieDetail.poster_path ? `https://image.tmdb.org/t/p/w500${movieDetail.poster_path}` : _images_no_image_png__WEBPACK_IMPORTED_MODULE_2__;\r\n const genres = movieDetail.genres.map((genre) => genre.name).join(', ');\r\n const modalElement = document.createElement('div');\r\n modalElement.classList.add('modal', 'modal--open');\r\n const modalHTML = `\r\n
\r\n
\r\n
\r\n

${movieDetail.title}

\r\n \r\n
\r\n
\r\n \"포스터\r\n
\r\n
\r\n
\r\n

${genres}

\r\n

${movieDetail.vote_average.toFixed(2)}

\r\n
\r\n

${movieDetail.overview}

\r\n
\r\n
\r\n

내 별점

\r\n
\r\n \r\n \r\n \r\n \r\n \r\n
\r\n

0

\r\n

남겨주세요

\r\n
\r\n
\r\n
\r\n
\r\n `;\r\n modalElement.innerHTML = modalHTML;\r\n document.body.appendChild(modalElement);\r\n __classPrivateFieldSet(this, _Modal_modalElement, modalElement, \"f\");\r\n modalElement.addEventListener('click', (event) => {\r\n this.handleModalClick(event);\r\n const button = event.target;\r\n const starButtons = modalElement.querySelectorAll('.my-vote-body button img');\r\n const starIndex = Array.from(starButtons).findIndex((btn) => btn === button);\r\n if (starIndex !== -1) {\r\n this.handleStarClick(starIndex);\r\n }\r\n });\r\n });\r\n }\r\n openModal() {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n if (__classPrivateFieldGet(this, _Modal_isOpen, \"f\"))\r\n return;\r\n yield this.generateModal();\r\n __classPrivateFieldSet(this, _Modal_isOpen, true, \"f\");\r\n });\r\n }\r\n closeModal() {\r\n if (!__classPrivateFieldGet(this, _Modal_isOpen, \"f\"))\r\n return;\r\n if (__classPrivateFieldGet(this, _Modal_modalElement, \"f\")) {\r\n __classPrivateFieldGet(this, _Modal_modalElement, \"f\").remove();\r\n __classPrivateFieldSet(this, _Modal_modalElement, null, \"f\");\r\n }\r\n __classPrivateFieldSet(this, _Modal_isOpen, false, \"f\");\r\n }\r\n handleModalClick(event) {\r\n if (!(event.target instanceof HTMLElement))\r\n return;\r\n const modalBackDrop = document.querySelector('.modal-backdrop');\r\n const modalCloseButton = event.target.closest('.modal-close-button');\r\n if (event.target === modalBackDrop || event.target === modalCloseButton) {\r\n this.closeModal();\r\n }\r\n }\r\n handleStarClick(starIndex) {\r\n var _a, _b, _c;\r\n const myVoteNumber = (_a = __classPrivateFieldGet(this, _Modal_modalElement, \"f\")) === null || _a === void 0 ? void 0 : _a.querySelector('.my-vote-number');\r\n const myVoteDescription = (_b = __classPrivateFieldGet(this, _Modal_modalElement, \"f\")) === null || _b === void 0 ? void 0 : _b.querySelector('.my-vote-description');\r\n if (!myVoteNumber || !myVoteDescription)\r\n return;\r\n const starButtons = (_c = __classPrivateFieldGet(this, _Modal_modalElement, \"f\")) === null || _c === void 0 ? void 0 : _c.querySelectorAll('.my-vote-body button img');\r\n if (!starButtons)\r\n return;\r\n starButtons.forEach((starButton, index) => {\r\n if (index <= starIndex) {\r\n starButton.setAttribute('src', _images_star_filled_png__WEBPACK_IMPORTED_MODULE_3__);\r\n }\r\n else {\r\n starButton.setAttribute('src', _images_star_empty_png__WEBPACK_IMPORTED_MODULE_4__);\r\n }\r\n });\r\n const myVoteKey = (starIndex + 1) * 2;\r\n myVoteNumber.textContent = myVoteKey.toString();\r\n myVoteDescription.textContent = _constants__WEBPACK_IMPORTED_MODULE_1__.VOTE[myVoteKey];\r\n }\r\n static getInstance(movieId) {\r\n if (!Modal.instance || __classPrivateFieldGet(Modal.instance, _Modal_movieId, \"f\") !== movieId) {\r\n Modal.instance = new Modal(movieId);\r\n }\r\n return Modal.instance;\r\n }\r\n}\r\n_Modal_isOpen = new WeakMap(), _Modal_modalElement = new WeakMap(), _Modal_movieId = new WeakMap();\r\nModal.instance = null;\r\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/components/Modal.ts?"); + +/***/ }), + +/***/ "./src/components/MovieCard.ts": +/*!*************************************!*\ + !*** ./src/components/MovieCard.ts ***! + \*************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ MovieCard)\n/* harmony export */ });\n/* harmony import */ var _images_no_image_png__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../images/no-image.png */ \"./src/images/no-image.png\");\n/* harmony import */ var _images_star_filled_png__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../images/star_filled.png */ \"./src/images/star_filled.png\");\nvar __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n};\r\nvar __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n};\r\nvar _MovieCard_instances, _MovieCard_liElement, _MovieCard_movieId, _MovieCard_movie, _MovieCard_generateMovieItem, _MovieCard_generateSkeletonMovieItem;\r\n\r\n\r\nclass MovieCard {\r\n constructor({ classes, movie }) {\r\n _MovieCard_instances.add(this);\r\n _MovieCard_liElement.set(this, document.createElement('li'));\r\n _MovieCard_movieId.set(this, void 0);\r\n _MovieCard_movie.set(this, void 0);\r\n if (classes)\r\n __classPrivateFieldGet(this, _MovieCard_liElement, \"f\").classList.add(...classes);\r\n if (movie) {\r\n __classPrivateFieldSet(this, _MovieCard_movie, movie, \"f\");\r\n __classPrivateFieldSet(this, _MovieCard_movieId, movie.id, \"f\");\r\n __classPrivateFieldGet(this, _MovieCard_instances, \"m\", _MovieCard_generateMovieItem).call(this, __classPrivateFieldGet(this, _MovieCard_movie, \"f\"));\r\n }\r\n else {\r\n __classPrivateFieldGet(this, _MovieCard_instances, \"m\", _MovieCard_generateSkeletonMovieItem).call(this);\r\n }\r\n }\r\n get element() {\r\n return __classPrivateFieldGet(this, _MovieCard_liElement, \"f\");\r\n }\r\n}\r\n_MovieCard_liElement = new WeakMap(), _MovieCard_movieId = new WeakMap(), _MovieCard_movie = new WeakMap(), _MovieCard_instances = new WeakSet(), _MovieCard_generateMovieItem = function _MovieCard_generateMovieItem(movie) {\r\n const posterPath = movie.poster_path\r\n ? `https://image.tmdb.org/t/p/w220_and_h330_face${movie.poster_path}`\r\n : _images_no_image_png__WEBPACK_IMPORTED_MODULE_0__;\r\n const element = `\r\n \r\n
\r\n \r\n

${movie.title}

\r\n

${movie.vote_average.toFixed(2)}\"별점\"

\r\n
\r\n
\r\n `;\r\n __classPrivateFieldGet(this, _MovieCard_liElement, \"f\").innerHTML = element;\r\n}, _MovieCard_generateSkeletonMovieItem = function _MovieCard_generateSkeletonMovieItem() {\r\n const element = ` \r\n
\r\n
\r\n
\r\n
\r\n
\r\n `;\r\n __classPrivateFieldGet(this, _MovieCard_liElement, \"f\").innerHTML = element;\r\n};\r\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/components/MovieCard.ts?"); + +/***/ }), + +/***/ "./src/components/SearchBox.ts": +/*!*************************************!*\ + !*** ./src/components/SearchBox.ts ***! + \*************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ SearchBox)\n/* harmony export */ });\nvar __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n};\r\nvar __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n};\r\nvar _SearchBox_instances, _SearchBox_searchBoxElement, _SearchBox_searchInputSubmit, _SearchBox_generateInput, _SearchBox_generateButton, _SearchBox_handleSubmit, _SearchBox_addFormEvent;\r\nclass SearchBox {\r\n constructor({ searchInputSubmit }) {\r\n _SearchBox_instances.add(this);\r\n _SearchBox_searchBoxElement.set(this, document.createElement('form'));\r\n _SearchBox_searchInputSubmit.set(this, void 0);\r\n __classPrivateFieldSet(this, _SearchBox_searchInputSubmit, searchInputSubmit, \"f\");\r\n __classPrivateFieldGet(this, _SearchBox_searchBoxElement, \"f\").classList.add('search-box');\r\n __classPrivateFieldGet(this, _SearchBox_instances, \"m\", _SearchBox_generateInput).call(this);\r\n __classPrivateFieldGet(this, _SearchBox_instances, \"m\", _SearchBox_generateButton).call(this);\r\n __classPrivateFieldGet(this, _SearchBox_instances, \"m\", _SearchBox_addFormEvent).call(this);\r\n }\r\n get element() {\r\n return __classPrivateFieldGet(this, _SearchBox_searchBoxElement, \"f\");\r\n }\r\n}\r\n_SearchBox_searchBoxElement = new WeakMap(), _SearchBox_searchInputSubmit = new WeakMap(), _SearchBox_instances = new WeakSet(), _SearchBox_generateInput = function _SearchBox_generateInput() {\r\n const input = document.createElement('input');\r\n input.type = 'text';\r\n input.placeholder = '검색';\r\n input.name = 'query';\r\n __classPrivateFieldGet(this, _SearchBox_searchBoxElement, \"f\").appendChild(input);\r\n}, _SearchBox_generateButton = function _SearchBox_generateButton() {\r\n const button = document.createElement('button');\r\n button.classList.add('search-button');\r\n button.textContent = '검색';\r\n __classPrivateFieldGet(this, _SearchBox_searchBoxElement, \"f\").appendChild(button);\r\n}, _SearchBox_handleSubmit = function _SearchBox_handleSubmit(event) {\r\n event.preventDefault();\r\n const target = event.target;\r\n __classPrivateFieldGet(this, _SearchBox_searchInputSubmit, \"f\").call(this, target.query.value);\r\n}, _SearchBox_addFormEvent = function _SearchBox_addFormEvent() {\r\n __classPrivateFieldGet(this, _SearchBox_searchBoxElement, \"f\").addEventListener('submit', __classPrivateFieldGet(this, _SearchBox_instances, \"m\", _SearchBox_handleSubmit).bind(this));\r\n};\r\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/components/SearchBox.ts?"); + +/***/ }), + +/***/ "./src/constants.ts": +/*!**************************!*\ + !*** ./src/constants.ts ***! + \**************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"ERROR_2XX\": () => (/* binding */ ERROR_2XX),\n/* harmony export */ \"ERROR_4XX\": () => (/* binding */ ERROR_4XX),\n/* harmony export */ \"ERROR_5XX\": () => (/* binding */ ERROR_5XX),\n/* harmony export */ \"SKELETON_UI_MOBILE\": () => (/* binding */ SKELETON_UI_MOBILE),\n/* harmony export */ \"SKELETON_UI_PC\": () => (/* binding */ SKELETON_UI_PC),\n/* harmony export */ \"SKELETON_UI_TABLET\": () => (/* binding */ SKELETON_UI_TABLET),\n/* harmony export */ \"VOTE\": () => (/* binding */ VOTE)\n/* harmony export */ });\nconst SKELETON_UI_PC = 8;\r\nconst SKELETON_UI_TABLET = 6;\r\nconst SKELETON_UI_MOBILE = 4;\r\nconst ERROR_2XX = '2';\r\nconst ERROR_4XX = '4';\r\nconst ERROR_5XX = '5';\r\nconst VOTE = {\r\n 2: '최악이예요',\r\n 4: '별로예요',\r\n 6: '보통이에요',\r\n 8: '재미있어요',\r\n 10: '명작이에요',\r\n};\r\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/constants.ts?"); + +/***/ }), + +/***/ "./src/index.js": +/*!**********************!*\ + !*** ./src/index.js ***! + \**********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _App__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./App */ \"./src/App.ts\");\n/* harmony import */ var _css_reset_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./css/reset.css */ \"./src/css/reset.css\");\n/* harmony import */ var _css_theme_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./css/theme.css */ \"./src/css/theme.css\");\n/* harmony import */ var _css_common_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./css/common.css */ \"./src/css/common.css\");\n/* harmony import */ var _css_modal_css__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./css/modal.css */ \"./src/css/modal.css\");\n\r\n\r\n\r\n\r\n\r\nconst app = new _App__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\r\napp.run();\r\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/index.js?"); + +/***/ }), + +/***/ "./src/store/API.ts": +/*!**************************!*\ + !*** ./src/store/API.ts ***! + \**************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"fetchMovieDetail\": () => (/* binding */ fetchMovieDetail),\n/* harmony export */ \"fetchPopularMovies\": () => (/* binding */ fetchPopularMovies),\n/* harmony export */ \"fetchSearchMovies\": () => (/* binding */ fetchSearchMovies)\n/* harmony export */ });\nvar __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n};\r\nconst BASE_URL = 'https://api.themoviedb.org/3';\r\nconst API_KEY = \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJmMDU4ZjVjNjhkNjdjMDBjNWIwMzhlZjBkNDFiZjZhNCIsInN1YiI6IjY1MGEwOTUzYWVkZTU5MDExYmU4MDA5YyIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.ZRuD_W18pOuRGc_wiePuFmHrkcTvzF32PBQUFPHjhLc\";\r\nconst options = {\r\n method: 'GET',\r\n headers: {\r\n accept: 'application/json',\r\n Authorization: `Bearer ${API_KEY}`,\r\n },\r\n};\r\nfunction fetchPopularMovies(pageCount) {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n const response = yield fetch(`${BASE_URL}/movie/popular?language=ko&page=${pageCount}`, options);\r\n return response;\r\n });\r\n}\r\nfunction fetchSearchMovies(query, presentPage) {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n const response = yield fetch(`${BASE_URL}/search/movie?query=${query}&language=ko&page=${presentPage}`, options);\r\n return response;\r\n });\r\n}\r\nfunction fetchMovieDetail(movieId) {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n const response = yield fetch(`${BASE_URL}/movie/${movieId}?language=ko`, options);\r\n return response.json();\r\n });\r\n}\r\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/store/API.ts?"); + +/***/ }), + +/***/ "./src/store/MovieStore.ts": +/*!*********************************!*\ + !*** ./src/store/MovieStore.ts ***! + \*********************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _API__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./API */ \"./src/store/API.ts\");\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../constants */ \"./src/constants.ts\");\n/* harmony import */ var _components_ErrorRender__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../components/ErrorRender */ \"./src/components/ErrorRender.ts\");\nvar __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n};\r\nvar __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n};\r\nvar __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n};\r\nvar _MovieStore_instances, _MovieStore_moviesData, _MovieStore_pageCount, _MovieStore_fetchMoviesData, _MovieStore_pushNewData;\r\n\r\n\r\n\r\nclass MovieStore {\r\n constructor() {\r\n _MovieStore_instances.add(this);\r\n _MovieStore_moviesData.set(this, void 0);\r\n _MovieStore_pageCount.set(this, 1);\r\n __classPrivateFieldSet(this, _MovieStore_moviesData, [], \"f\");\r\n }\r\n getMovies() {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n try {\r\n const responseData = yield __classPrivateFieldGet(this, _MovieStore_instances, \"m\", _MovieStore_fetchMoviesData).call(this);\r\n __classPrivateFieldGet(this, _MovieStore_instances, \"m\", _MovieStore_pushNewData).call(this, responseData);\r\n return responseData;\r\n }\r\n catch (error) {\r\n console.error(error);\r\n }\r\n });\r\n }\r\n increasePageCount() {\r\n __classPrivateFieldSet(this, _MovieStore_pageCount, __classPrivateFieldGet(this, _MovieStore_pageCount, \"f\") + 1, \"f\");\r\n }\r\n get movies() {\r\n return __classPrivateFieldGet(this, _MovieStore_moviesData, \"f\");\r\n }\r\n}\r\n_MovieStore_moviesData = new WeakMap(), _MovieStore_pageCount = new WeakMap(), _MovieStore_instances = new WeakSet(), _MovieStore_fetchMoviesData = function _MovieStore_fetchMoviesData() {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n const response = yield (0,_API__WEBPACK_IMPORTED_MODULE_0__.fetchPopularMovies)(__classPrivateFieldGet(this, _MovieStore_pageCount, \"f\"));\r\n if (!response.ok) {\r\n throw new _components_ErrorRender__WEBPACK_IMPORTED_MODULE_2__[\"default\"](String(response.status)).renderError();\r\n }\r\n const responseJSON = yield response.json();\r\n if (String(response.status)[0] === _constants__WEBPACK_IMPORTED_MODULE_1__.ERROR_2XX && responseJSON.results.length === 0) {\r\n throw new _components_ErrorRender__WEBPACK_IMPORTED_MODULE_2__[\"default\"](String(response.status)).renderError();\r\n }\r\n return responseJSON.results;\r\n });\r\n}, _MovieStore_pushNewData = function _MovieStore_pushNewData(data) {\r\n if (data) {\r\n __classPrivateFieldGet(this, _MovieStore_moviesData, \"f\").push(...data);\r\n }\r\n};\r\nconst movieStore = new MovieStore();\r\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (movieStore);\r\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/store/MovieStore.ts?"); + +/***/ }), + +/***/ "./src/store/SearchMovieStore.ts": +/*!***************************************!*\ + !*** ./src/store/SearchMovieStore.ts ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _API__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./API */ \"./src/store/API.ts\");\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../constants */ \"./src/constants.ts\");\n/* harmony import */ var _components_ErrorRender__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../components/ErrorRender */ \"./src/components/ErrorRender.ts\");\nvar __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n};\r\nvar __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n};\r\nvar __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n};\r\nvar _SearchMovieStore_instances, _SearchMovieStore_searchMoviesData, _SearchMovieStore_totalPages, _SearchMovieStore_query, _SearchMovieStore_presentPage, _SearchMovieStore_fetchSearchData, _SearchMovieStore_pushNewData;\r\n\r\n\r\n\r\nclass SearchMovieStore {\r\n constructor() {\r\n _SearchMovieStore_instances.add(this);\r\n _SearchMovieStore_searchMoviesData.set(this, void 0);\r\n _SearchMovieStore_totalPages.set(this, 0);\r\n _SearchMovieStore_query.set(this, '');\r\n _SearchMovieStore_presentPage.set(this, 1);\r\n __classPrivateFieldSet(this, _SearchMovieStore_searchMoviesData, [], \"f\");\r\n }\r\n searchMovies() {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n try {\r\n const responseData = yield __classPrivateFieldGet(this, _SearchMovieStore_instances, \"m\", _SearchMovieStore_fetchSearchData).call(this);\r\n const { results } = responseData;\r\n __classPrivateFieldSet(this, _SearchMovieStore_totalPages, responseData.total_pages, \"f\");\r\n __classPrivateFieldGet(this, _SearchMovieStore_instances, \"m\", _SearchMovieStore_pushNewData).call(this, results);\r\n return results;\r\n }\r\n catch (error) {\r\n console.error(error);\r\n }\r\n });\r\n }\r\n increasePageCount() {\r\n __classPrivateFieldSet(this, _SearchMovieStore_presentPage, __classPrivateFieldGet(this, _SearchMovieStore_presentPage, \"f\") + 1, \"f\");\r\n }\r\n get movies() {\r\n return __classPrivateFieldGet(this, _SearchMovieStore_searchMoviesData, \"f\");\r\n }\r\n get totalPages() {\r\n return __classPrivateFieldGet(this, _SearchMovieStore_totalPages, \"f\");\r\n }\r\n get presentPage() {\r\n return __classPrivateFieldGet(this, _SearchMovieStore_presentPage, \"f\");\r\n }\r\n get query() {\r\n return __classPrivateFieldGet(this, _SearchMovieStore_query, \"f\");\r\n }\r\n set query(query) {\r\n __classPrivateFieldSet(this, _SearchMovieStore_query, query, \"f\");\r\n __classPrivateFieldSet(this, _SearchMovieStore_presentPage, 1, \"f\");\r\n }\r\n}\r\n_SearchMovieStore_searchMoviesData = new WeakMap(), _SearchMovieStore_totalPages = new WeakMap(), _SearchMovieStore_query = new WeakMap(), _SearchMovieStore_presentPage = new WeakMap(), _SearchMovieStore_instances = new WeakSet(), _SearchMovieStore_fetchSearchData = function _SearchMovieStore_fetchSearchData() {\r\n return __awaiter(this, void 0, void 0, function* () {\r\n const response = yield (0,_API__WEBPACK_IMPORTED_MODULE_0__.fetchSearchMovies)(__classPrivateFieldGet(this, _SearchMovieStore_query, \"f\"), __classPrivateFieldGet(this, _SearchMovieStore_presentPage, \"f\"));\r\n if (!response.ok) {\r\n throw new _components_ErrorRender__WEBPACK_IMPORTED_MODULE_2__[\"default\"](String(response.status)).renderError();\r\n }\r\n const responseJSON = yield response.json();\r\n if (String(response.status)[0] === _constants__WEBPACK_IMPORTED_MODULE_1__.ERROR_2XX && responseJSON.results.length === 0) {\r\n throw new _components_ErrorRender__WEBPACK_IMPORTED_MODULE_2__[\"default\"](String(response.status)).renderError();\r\n }\r\n return responseJSON;\r\n });\r\n}, _SearchMovieStore_pushNewData = function _SearchMovieStore_pushNewData(data) {\r\n if (data) {\r\n __classPrivateFieldGet(this, _SearchMovieStore_searchMoviesData, \"f\").push(...data);\r\n }\r\n};\r\nconst searchMovieStore = new SearchMovieStore();\r\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (searchMovieStore);\r\n\n\n//# sourceURL=webpack://javascript-movie-review/./src/store/SearchMovieStore.ts?"); + +/***/ }), + +/***/ "./src/images/logo.png": +/*!*****************************!*\ + !*** ./src/images/logo.png ***! + \*****************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +eval("module.exports = __webpack_require__.p + \"2e162b4fefb34cd7ed8d.png\";\n\n//# sourceURL=webpack://javascript-movie-review/./src/images/logo.png?"); + +/***/ }), + +/***/ "./src/images/modal_close_button.png": +/*!*******************************************!*\ + !*** ./src/images/modal_close_button.png ***! + \*******************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +eval("module.exports = __webpack_require__.p + \"e9a379619c759f886f7c.png\";\n\n//# sourceURL=webpack://javascript-movie-review/./src/images/modal_close_button.png?"); + +/***/ }), + +/***/ "./src/images/no-image.png": +/*!*********************************!*\ + !*** ./src/images/no-image.png ***! + \*********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +eval("module.exports = __webpack_require__.p + \"06f0f15cfcb8d681b62c.png\";\n\n//# sourceURL=webpack://javascript-movie-review/./src/images/no-image.png?"); + +/***/ }), + +/***/ "./src/images/search_button.png": +/*!**************************************!*\ + !*** ./src/images/search_button.png ***! + \**************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +eval("module.exports = __webpack_require__.p + \"f1bd4269f4446ceae306.png\";\n\n//# sourceURL=webpack://javascript-movie-review/./src/images/search_button.png?"); + +/***/ }), + +/***/ "./src/images/star_empty.png": +/*!***********************************!*\ + !*** ./src/images/star_empty.png ***! + \***********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +eval("module.exports = __webpack_require__.p + \"6c9611deedf4b85849c9.png\";\n\n//# sourceURL=webpack://javascript-movie-review/./src/images/star_empty.png?"); + +/***/ }), + +/***/ "./src/images/star_filled.png": +/*!************************************!*\ + !*** ./src/images/star_filled.png ***! + \************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +eval("module.exports = __webpack_require__.p + \"6328741810b732410eec.png\";\n\n//# sourceURL=webpack://javascript-movie-review/./src/images/star_filled.png?"); + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ id: moduleId, +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat get default export */ +/******/ (() => { +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = (module) => { +/******/ var getter = module && module.__esModule ? +/******/ () => (module['default']) : +/******/ () => (module); +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/global */ +/******/ (() => { +/******/ __webpack_require__.g = (function() { +/******/ if (typeof globalThis === 'object') return globalThis; +/******/ try { +/******/ return this || new Function('return this')(); +/******/ } catch (e) { +/******/ if (typeof window === 'object') return window; +/******/ } +/******/ })(); +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ (() => { +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/publicPath */ +/******/ (() => { +/******/ var scriptUrl; +/******/ if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + ""; +/******/ var document = __webpack_require__.g.document; +/******/ if (!scriptUrl && document) { +/******/ if (document.currentScript) +/******/ scriptUrl = document.currentScript.src +/******/ if (!scriptUrl) { +/******/ var scripts = document.getElementsByTagName("script"); +/******/ if(scripts.length) scriptUrl = scripts[scripts.length - 1].src +/******/ } +/******/ } +/******/ // When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration +/******/ // or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic. +/******/ if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser"); +/******/ scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/"); +/******/ __webpack_require__.p = scriptUrl; +/******/ })(); +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ __webpack_require__.b = document.baseURI || self.location.href; +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ var installedChunks = { +/******/ "main": 0 +/******/ }; +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ // no on chunks loaded +/******/ +/******/ // no jsonp function +/******/ })(); +/******/ +/******/ /* webpack/runtime/nonce */ +/******/ (() => { +/******/ __webpack_require__.nc = undefined; +/******/ })(); +/******/ +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module can't be inlined because the eval devtool is used. +/******/ var __webpack_exports__ = __webpack_require__("./src/index.js"); +/******/ +/******/ })() +; \ No newline at end of file diff --git a/dist/e9a379619c759f886f7c.png b/dist/e9a379619c759f886f7c.png new file mode 100644 index 000000000..cbe1b680f Binary files /dev/null and b/dist/e9a379619c759f886f7c.png differ diff --git a/dist/f1bd4269f4446ceae306.png b/dist/f1bd4269f4446ceae306.png new file mode 100644 index 000000000..65c9ab48c Binary files /dev/null and b/dist/f1bd4269f4446ceae306.png differ diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 000000000..d5b3daad9 --- /dev/null +++ b/dist/index.html @@ -0,0 +1,26 @@ + + + + + + + + 영화관 + + + +
+
+

+ +

+
+
+
+

+
    +
    +
    +
    + + diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 79089188b..cd09544c8 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -4,7 +4,7 @@ - 실제 동작하는 API를 통한 비동기 통신 - UX 경험 개선을 위한 더 보기(페이징) 구현 -## 구현할 기능 목록 +## STEP1 구현할 기능 목록 ### 1. 🎬 영화 목록 조회 (인기순) @@ -32,3 +32,25 @@ - [x] 검색 결과가 없을 경우 (응답은 있으나) - [x] HTTP 4xx 오류일 때 (클라이언트 오류) - [x] HTTP 5xx 오류일 때 (서버 오류) + +## STEP2 구현할 기능 목록 + +### 1. 📺 영화 상세정보 조회 + +- [x] 영화 포스터나 제목을 클릭하면 자세한 예고편이나 줄거리 등의 정보를 보여준다. +- [x] API에서 제공하는 항목을 활용하여 상세 정보를 보여주는 모달 창을 구현한다. +- [x] 키보드의 ESC 키를 누르면 모달 창을 닫을 수 있는 등 사용성을 고려한다. + +### 2. ⭐️ 별점 매기기 + +- TMDB API 요청과는 관련 없습니다. +- [x] 사용자는 영화에 대해 별점을 줄 수 있다. + - [x] 새로고침하더라도 사용자가 남긴 별점은 유지되어야 한다. + - [x] 별점은 5개로 구성되어 있으며 한 개당 2점이며 1점 단위는 고려하지 않는다. + - 2점, 4점, 6점, 8점, 10점 + +### 3. 📐 UI⁄UX 개선하기 + +- [x] 영화 목록과 영화 상세 정보가 뜨는 모달창에 대한 반응형 레이아웃을 구성한다. +- [x] 영화 목록의 더보기 버튼을 무한 스크롤 방식으로 변경한다. + - 검색 결과 화면에서 사용자가 브라우저 화면의 끝에 도달하면 그 다음 20개의 목록을 서버에 요청하여 추가로 불러올 수 있다. diff --git a/index.html b/index.html index 288df3255..cc50e7242 100644 --- a/index.html +++ b/index.html @@ -5,16 +5,14 @@ - - - 영화관 +

    - +

    @@ -22,6 +20,7 @@

    +
    diff --git a/src/App.ts b/src/App.ts index ac9b44323..f96604892 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,22 +1,57 @@ import { Movie } from './index.d'; -import { SKELETON_UI_FIXED } from './constants'; +import { SKELETON_UI_PC, SKELETON_UI_TABLET, SKELETON_UI_MOBILE, MOBILE_SIZE, TABLET_SIZE } from './constants'; -import MoreButton from './components/MoreButton'; -import MovieCard from './components/MovieCard'; import movieStore from './store/MovieStore'; -import SearchBox from './components/SearchBox'; import searchMovieStore from './store/SearchMovieStore'; +import SearchBox from './components/SearchBox'; +import MovieCard from './components/MovieCard'; +import Modal from './components/Modal'; + +import Logo from './images/logo.png'; + type Tpage = 'popular' | 'search'; export default class App { #pageType: Tpage = 'popular'; + #observer: IntersectionObserver | null = null; + + #isLoading: boolean = false; + + #skeletonBySize: number = SKELETON_UI_PC; + async run() { + this.#insertLogo(); this.#generateMovieList(); this.#generateSearchBox(); this.#addHomeButtonEvent(); + this.#initEventListeners(); + this.#setupIntersectionObserver(); + this.#goToTop(); + } + + #insertLogo() { + const homeButton = document.getElementById('home-button'); + const imgElement = document.createElement('img'); + + imgElement.src = Logo; + imgElement.alt = 'MovieList 로고'; + + homeButton?.appendChild(imgElement); + } + + #getSkeletonCount() { + const width = window.innerWidth; + + if (width <= MOBILE_SIZE) { + return SKELETON_UI_MOBILE; + } + if (width <= TABLET_SIZE) { + return SKELETON_UI_TABLET; + } + return this.#skeletonBySize; } #generateMovieList() { @@ -35,14 +70,22 @@ export default class App { async #generateItemList(title: string, fetchData: () => Promise, store: any) { this.#changeTitle(title); this.#removePreviousError(); + const ulElement = document.querySelector('ul.item-list'); + if (!ulElement) return; - if (ulElement) { - this.#generateSkeletonUI(ulElement as HTMLElement); - const newData = await fetchData(); - this.#removeSkeletonUI(); - this.#appendMovieCard(newData, ulElement as HTMLElement); - } + const skeletonCount = this.#getSkeletonCount(); + this.#generateSkeletonUI(ulElement as HTMLElement, skeletonCount); + + const newData = await fetchData(); + + this.#removeSkeletonUI(); + + if (!newData) return; + + this.#appendMovieCard(newData, ulElement as HTMLElement); + + this.#observeSentinel(); } #changeTitle(title: string) { @@ -62,20 +105,16 @@ export default class App { ulElement?.appendChild(card.element); }); - this.#generateMoreButton(); + this.#addSentinel(); } - // eslint-disable-next-line max-lines-per-function - #generateSkeletonUI(ulElement: HTMLElement) { - this.#removeMoreButton(); - + #generateSkeletonUI(ulElement: HTMLElement, skeletonCount: number) { const fragment = new DocumentFragment(); - for (let i = 0; i < SKELETON_UI_FIXED; i++) { + for (let i = 0; i < skeletonCount; i++) { const card = new MovieCard({ classes: ['skeleton-container'], }); - fragment.appendChild(card.element); } @@ -92,36 +131,70 @@ export default class App { } } - /* eslint-disable max-lines-per-function */ - #generateMoreButton() { - this.#removeMoreButton(); + #setupIntersectionObserver() { + const options = { + root: null, + rootMargin: '0px', + threshold: 0.2, + }; - if (searchMovieStore.presentPage === searchMovieStore.totalPages) return; + this.#observer = new IntersectionObserver(this.#handleIntersection, options); - const itemView = document.querySelector('section.item-view'); - - const moreBtn = new MoreButton({ - onClick: () => { - if (this.#pageType === 'popular') { - movieStore.increasePageCount(); - this.#generateMovieList(); - } else { - searchMovieStore.increasePageCount(); - this.#generateSearchMovieList(); - } - }, - }); + this.#observeSentinel(); + } + + #addSentinel() { + const ulElement = document.querySelector('ul.item-list'); - itemView?.appendChild(moreBtn.element); + if (ulElement) { + const sentinel = document.createElement('li'); + sentinel.classList.add('sentinel'); + ulElement.appendChild(sentinel); + this.#observeSentinel(); + } } - #removeMoreButton() { - const moreButton = document.getElementById('more-button'); - if (moreButton) { - moreButton.parentNode?.removeChild(moreButton); + #removeSentinel() { + const sentinelElement = document.querySelector('li.sentinel'); + if (sentinelElement) { + sentinelElement.remove(); } } + #observeSentinel() { + const sentinel = document.querySelector('.sentinel'); + if (sentinel && this.#observer) { + this.#observer.observe(sentinel); + } + } + + #handleIntersection = (entries: IntersectionObserverEntry[]) => { + entries.forEach((entry) => { + if (entry.isIntersecting && entry.intersectionRatio >= 0.2 && !this.#isLoading) { + this.#loadMoreMovies(); + } + }); + }; + + // eslint-disable-next-line max-lines-per-function + async #loadMoreMovies() { + if (searchMovieStore.presentPage === searchMovieStore.totalPages) return; + + this.#removeSentinel(); + + this.#isLoading = true; + + if (this.#pageType === 'popular') { + await movieStore.increasePageCount(); + await this.#generateMovieList(); + } else { + await searchMovieStore.increasePageCount(); + await this.#generateSearchMovieList(); + } + + this.#isLoading = false; + } + #removePreviousError() { const previousError = document.getElementById('error-page'); @@ -130,6 +203,7 @@ export default class App { } } + // eslint-disable-next-line max-lines-per-function #generateSearchBox() { const header = document.querySelector('header'); const ulElement = document.querySelector('ul.item-list'); @@ -154,7 +228,6 @@ export default class App { this.#pageType = 'popular'; this.#changeTitle('지금 인기 있는 영화'); this.#removePreviousError(); - this.#removeMoreButton(); this.#renderAllMovieList(); }); } @@ -169,5 +242,34 @@ export default class App { ulElement.innerHTML = ''; this.#appendMovieCard(movieDatas, ulElement as HTMLElement); + this.#observeSentinel(); + } + + #initEventListeners() { + const itemList = document.querySelector('ul.item-list'); + + if (itemList) { + itemList.addEventListener('click', this.#handleMovieCardClick.bind(this)); + } + } + + #handleMovieCardClick(event: any) { + const clickedElement = event.target.closest('.item-card'); + + if (clickedElement) { + const movieId = Number(clickedElement.dataset.movieid); + const modal = Modal.getInstance(movieId); + modal.openModal(); + } + } + + #goToTop() { + const topButton = document.querySelector('#goToTop'); + + if (topButton) { + topButton.addEventListener('click', () => { + window.scrollTo(0, 0); + }); + } } } diff --git a/src/components/ErrorRender.ts b/src/components/ErrorRender.ts index 157d90361..5c2abbb8c 100644 --- a/src/components/ErrorRender.ts +++ b/src/components/ErrorRender.ts @@ -19,6 +19,7 @@ export default class ErrorRender { if (this.#status[0] === ERROR_2XX) this.#renderNoResult(); if (this.#status[0] === ERROR_4XX) this.#renderClientError(); if (this.#status[0] === ERROR_5XX) this.#renderServerError(); + return new Error('오류'); } #renderNoResult() { diff --git a/src/components/Modal.ts b/src/components/Modal.ts new file mode 100644 index 000000000..f235629c0 --- /dev/null +++ b/src/components/Modal.ts @@ -0,0 +1,107 @@ +import movieInfo from './MovieInfo'; +import VoteHandler from './VoteHandler'; + +export default class Modal { + #isOpen = false; + + #modalElement: HTMLElement | null = null; + + static instance: Modal | null = null; + + #movieId: number; + + #voteHandler: any; + + constructor(movieId: number) { + this.#movieId = movieId; + } + + // eslint-disable-next-line max-lines-per-function + async generateModal() { + this.#removeExistingModal(); + + const skeletonModalElement = movieInfo.generateSkeletonModal(); + + const movieDetail = await movieInfo.fetchMovieDetail(this.#movieId); + const modalContent = movieInfo.prepareModalContent(movieDetail); + const modalElement = movieInfo.createModalElement(modalContent); + + skeletonModalElement.remove(); + + document.body.appendChild(modalElement); + this.#modalElement = modalElement; + this.#setupModalEventListener(this.#modalElement); + + this.#voteHandler = new VoteHandler(this.#movieId, this.#modalElement); + } + + #removeExistingModal() { + if (!this.#modalElement) return; + + this.#modalElement.remove(); + this.#modalElement = null; + } + + // eslint-disable-next-line max-lines-per-function + #setupModalEventListener(modalElement: HTMLElement) { + modalElement.addEventListener('click', (event) => { + this.handleModalClick(event); + + const button = event.target; + const starButtons = modalElement.querySelectorAll('.my-vote-body button img'); + const starIndex = Array.from(starButtons).findIndex((btn) => btn === button); + + if (starIndex !== -1) { + this.#voteHandler.handleStarClick(starIndex); + } + }); + } + + async openModal() { + if (this.#isOpen) return; + + document.body.style.overflow = 'hidden'; + await this.generateModal(); + this.#voteHandler.VoteHandler(); + document.addEventListener('keydown', this.#handleKeyDown); + this.#isOpen = true; + } + + closeModal() { + if (!this.#isOpen) return; + + document.body.style.overflow = 'auto'; + this.removeKeydownEventListener(); + this.#removeExistingModal(); + this.#isOpen = false; + } + + #handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + this.closeModal(); + } + }; + + removeKeydownEventListener() { + document.removeEventListener('keydown', this.#handleKeyDown); + } + + handleModalClick(event: Event) { + if (!(event.target instanceof HTMLElement)) return; + + const modalBackDrop = document.querySelector('.modal-backdrop'); + const modalCloseButton = event.target.closest('.modal-close-button'); + + if (event.target === modalBackDrop || event.target === modalCloseButton) { + this.closeModal(); + } + } + + static getInstance(movieId: number) { + if (!Modal.instance || Modal.instance.#movieId !== movieId) { + Modal.instance = new Modal(movieId); + } + + return Modal.instance; + } +} diff --git a/src/components/MovieCard.ts b/src/components/MovieCard.ts index 8f0fe53ac..9fcd07c89 100644 --- a/src/components/MovieCard.ts +++ b/src/components/MovieCard.ts @@ -1,5 +1,8 @@ import { Movie } from '../index.d'; +import NoImage from '../images/no-image.png'; +import StarFilled from '../images/star_filled.png'; + interface Props { classes?: string[]; movie?: Movie; @@ -8,49 +11,57 @@ interface Props { export default class MovieCard { #liElement = document.createElement('li'); + #movieId!: number; + #movie; constructor({ classes, movie }: Props) { if (classes) this.#liElement.classList.add(...classes); + if (movie) { this.#movie = movie; + this.#movieId = movie.id; + this.#generateMovieItem(this.#movie); } else { this.#generateSkeletonMovieItem(); } } - /* eslint-disable max-lines-per-function */ #generateMovieItem(movie: Movie) { - const posterPath = movie.poster_path - ? `https:image.tmdb.org/t/p/w220_and_h330_face${movie.poster_path}` - : '../images/no-image.png'; + const thumbnail = movie.poster_path ? `https://image.tmdb.org/t/p/w220_and_h330_face${movie.poster_path}` : NoImage; + const { title } = movie; + const voteAverage = movie.vote_average.toFixed(2); + + this.#liElement.innerHTML = this.#createMovieItem(thumbnail, title, voteAverage); + } + + // eslint-disable-next-line max-lines-per-function + #createMovieItem(thumbnail: any, title: string, voteAverage: string) { const element = /* html */ ` - -
    - ${movie.title} -

    ${movie.title}

    -

    ${movie.vote_average.toFixed(2)}별점

    -
    -
    `; +
    + ${title} +

    ${title}

    +

    ${voteAverage}별점

    +
    + `; - this.#liElement.innerHTML = element; + return element; } #generateSkeletonMovieItem() { const element = /* html */ ` - -
    -
    -
    -
    -
    -
    `; +
    +
    +
    +
    +
    + `; this.#liElement.innerHTML = element; } diff --git a/src/components/MovieInfo.ts b/src/components/MovieInfo.ts new file mode 100644 index 000000000..e8140ab7b --- /dev/null +++ b/src/components/MovieInfo.ts @@ -0,0 +1,108 @@ +import { ModalContent } from '../index.d'; +import { fetchMovieDetail } from '../store/API'; + +import NoImage from '../images/no-image.png'; +import StarFilled from '../images/star_filled.png'; +import StarEmpty from '../images/star_empty.png'; + +class MovieInfo { + async fetchMovieDetail(movieId: number) { + const moviedetail = await fetchMovieDetail(movieId); + const movieDetail = await moviedetail.json(); + + return movieDetail; + } + + prepareModalContent(movieDetail: any) { + const { title, poster_path, genres, vote_average, overview } = movieDetail; + + return { + title, + posterPath: poster_path ? `https://image.tmdb.org/t/p/w500${poster_path}` : NoImage, + genres: genres.map((genre: any) => genre.name).join(', '), + voteAverage: vote_average.toFixed(2), + overview: overview || '해당 영화의 줄거리 정보가 없습니다.', + }; + } + + // eslint-disable-next-line max-lines-per-function + createModalElement(content: ModalContent) { + const { title, posterPath, genres, voteAverage, overview } = content; + const modalElement = document.createElement('div'); + modalElement.classList.add('modal', 'modal--open'); + + const modalHTML = /* html */ ` + + + `; + + modalElement.innerHTML = modalHTML; + return modalElement; + } + + generateSkeletonModal() { + const modalElement = this.createSkeletonModalElement(); + document.body.appendChild(modalElement); + + return modalElement; + } + + // eslint-disable-next-line max-lines-per-function + createSkeletonModalElement() { + const modalElement = document.createElement('div'); + modalElement.classList.add('modal', 'modal--open'); + + const modalHTML = /* html */ ` + + + `; + + modalElement.innerHTML = modalHTML; + return modalElement; + } +} + +const movieInfo = new MovieInfo(); +export default movieInfo; diff --git a/src/components/VoteHandler.ts b/src/components/VoteHandler.ts new file mode 100644 index 000000000..88afe2c7b --- /dev/null +++ b/src/components/VoteHandler.ts @@ -0,0 +1,83 @@ +import { VOTE } from '../constants'; + +import StarFilled from '../images/star_filled.png'; +import StarEmpty from '../images/star_empty.png'; + +export default class VoteHandler { + #movieId: number; + + #modalElement: HTMLElement | null = null; + + #myVoteResult: { [key: string]: number } = {}; + + constructor(movieId: number, modalElement: HTMLElement | null) { + this.#movieId = movieId; + this.#modalElement = modalElement; + + const savedVotes = localStorage.getItem('myVoteResult'); + if (savedVotes) { + this.#myVoteResult = JSON.parse(savedVotes); + } + } + + VoteHandler() { + const savedVotes = localStorage.getItem('myVoteResult'); + const voteForMovie = savedVotes ? JSON.parse(savedVotes)[this.#movieId] : null; + + if (voteForMovie) { + this.#updateVoteStar(voteForMovie); + this.#updateVoteText(voteForMovie); + } + } + + #updateVoteStar(voteForMovie: number) { + const starButtons = this.#modalElement?.querySelectorAll('.my-vote-body button img'); + + if (!starButtons) return; + + starButtons.forEach((starButton, index) => { + starButton.setAttribute('src', index < voteForMovie / 2 ? StarFilled : StarEmpty); + }); + } + + #updateVoteText(voteForMovie: number) { + const myVoteNumber = this.#modalElement?.querySelector('.my-vote-number'); + const myVoteDescription = this.#modalElement?.querySelector('.my-vote-description'); + + if (!myVoteNumber || !myVoteDescription) return; + + myVoteNumber.textContent = voteForMovie.toString(); + myVoteDescription.textContent = VOTE[voteForMovie]; + } + + handleStarClick(starIndex: number) { + const myVoteNumber = this.#modalElement?.querySelector('.my-vote-number'); + const myVoteDescription = this.#modalElement?.querySelector('.my-vote-description'); + + if (!myVoteNumber || !myVoteDescription) return; + + const starButtons = this.#modalElement?.querySelectorAll('.my-vote-body button img'); + if (!starButtons) return; + + this.updateStarButtons(starButtons, starIndex); + this.updateVoteInfo(myVoteNumber, myVoteDescription, starIndex); + } + + updateStarButtons(starButtons: NodeListOf, starIndex: number) { + starButtons.forEach((starButton, index) => { + starButton.setAttribute('src', index <= starIndex ? StarFilled : StarEmpty); + }); + } + + updateVoteInfo(myVoteNumber: Element, myVoteDescription: Element, starIndex: number) { + const newMyVoteNumber = myVoteNumber as HTMLElement; + const newMyVoteDescription = myVoteDescription as HTMLElement; + + const myVoteKey = (starIndex + 1) * 2; + newMyVoteNumber.textContent = myVoteKey.toString(); + newMyVoteDescription.textContent = VOTE[myVoteKey]; + + this.#myVoteResult[this.#movieId] = myVoteKey; + localStorage.setItem('myVoteResult', JSON.stringify(this.#myVoteResult)); + } +} diff --git a/src/constants.ts b/src/constants.ts index 9ac48cb7d..a0c1b28cf 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,19 @@ -export const SKELETON_UI_FIXED = 8; // 스켈레톤 UI 갯수 +export const SKELETON_UI_PC = 8; // 스켈레톤 UI 갯수 - pc +export const SKELETON_UI_TABLET = 6; // 스켈레톤 UI 갯수 - tablet +export const SKELETON_UI_MOBILE = 4; // 스켈레톤 UI 갯수 - mobile + +export const MOBILE_SIZE = 767; // mobile 사이즈 기준 +export const TABLET_SIZE = 1280; // tablet 사이즈 기준 export const ERROR_2XX = '2'; // 200번대 오류 export const ERROR_4XX = '4'; // 400번대 오류 export const ERROR_5XX = '5'; // 500번대 오류 + +export const VOTE: { [key: number]: string } = { + // 별점 + 2: '최악이예요', + 4: '별로예요', + 6: '보통이에요', + 8: '재미있어요', + 10: '명작이에요', +}; diff --git a/src/css/common.css b/src/css/common.css index 5f63384e0..dc16d59b2 100644 --- a/src/css/common.css +++ b/src/css/common.css @@ -3,8 +3,11 @@ } body { - font-size: 14px; + position: relative; background-color: var(--black); + + font-family: Roboto; + font-size: 14px; color: var(--white); } @@ -18,8 +21,9 @@ button { } #app { - margin-top: 150px; + margin-top: 120px; padding-bottom: 48px; + width: 100%; } *:focus { @@ -34,7 +38,8 @@ button { display: flex; flex-direction: column; justify-content: center; - width: 1200px; + max-width: 1200px; + margin: 0 auto; } @@ -59,6 +64,10 @@ button { flex-direction: column; } +.item-card:hover { + cursor: pointer; +} + .item-thumbnail { border-radius: 8px; width: 180px; @@ -66,6 +75,10 @@ button { background-size: contain; } +.item-thumbnail:hover { + box-shadow: 0px 0px 14px 5px rgba(255, 255, 255, 0.3); +} + .item-title { margin-top: 16px; font-size: 1.2rem; @@ -73,10 +86,12 @@ button { } .item-score { + display: flex; + align-items: center; + gap: 5px; margin-top: 16px; + font-size: 1.2rem; - display: inline-block; - vertical-align: middle; } .item-score::after { @@ -128,21 +143,22 @@ button.primary { } header { - width: 100%; - min-width: 1200px; - height: 72px; - background-color: var(--black); display: flex; justify-content: space-between; align-items: center; padding: 0 20px; - border-bottom: 1px solid var(--white); - margin-bottom: 48px; position: fixed; top: 0; left: 0; - z-index: 1000; + z-index: 10; + + border-bottom: 1px solid var(--white); + margin-bottom: 48px; + + width: 100vw; + height: 72px; + background-color: var(--black); } header h1 { @@ -165,6 +181,10 @@ header > .search-box { border-radius: 4px; } +header > .search-box:hover { + box-shadow: 0px 0px 8px 4px rgba(255, 255, 255, 0.3); +} + header .search-box > input { border: 0; } @@ -176,3 +196,151 @@ header .search-box > .search-button { background: url('../images/search_button.png') transparent no-repeat 0 1px; background-size: contain; } + +@media only screen and (min-width: 1280px) { + /* 1280px 이상: 피씨 스타일 */ + + .item-view { + display: flex; + flex-direction: column; + justify-content: center; + max-width: 1200px; + + margin: 0 auto; + } + + .item-view h2 { + font-size: 2rem; + font-weight: bold; + user-select: none; + } + + /* https://andrew.hedges.name/experiments/aspect_ratio/ */ + + .item-list { + display: grid; + margin: 48px 0; + grid-template-columns: repeat(4, 180px); + grid-column-gap: 160px; + grid-row-gap: 48px; + } +} + +@media only screen and (min-width: 768px) and (max-width: 1280px) { + /* 768px 이상 ~ 1280px 이하: 태블릿 스타일 */ + #app { + margin-top: 108px; + } + + .item-view { + display: flex; + flex-direction: column; + justify-content: center; + max-width: 674px; + margin: 0 auto; + } + + .item-list { + display: grid; + margin: 36px 0; + grid-template-columns: repeat(3, 180px); + grid-column-gap: 60px; + grid-row-gap: 55px; + } + + header { + min-width: 674px; + } +} + +@media only screen and (min-width: 390px) and (max-width: 767px) { + /* 390px 이상 ~ 768px 이하: 모바일 스타일 */ + #app { + margin-top: 100px; + } + + .item-view { + display: flex; + flex-direction: column; + justify-content: center; + max-width: 316px; + margin: 0 auto; + } + + .item-list { + display: grid; + margin: 24px 0; + grid-template-columns: repeat(2, 140px); + grid-column-gap: 50px; + grid-row-gap: 55px; + } + + .item-thumbnail { + border-radius: 15px; + width: 140px; + height: 220px; + background-size: contain; + } + + header { + min-width: 120px; + } +} + +@media only screen and (min-width: 320px) and (max-width: 390px) { + /* 320px 이상 ~ 390px 이하: 모바일 스타일 */ + #app { + margin-top: 100px; + } + + .item-view { + display: flex; + flex-direction: column; + justify-content: center; + max-width: 300px; + margin: 0 auto; + } + + .item-list { + display: grid; + margin: 24px 0; + grid-template-columns: repeat(2, 140px); + grid-column-gap: 24px; + grid-row-gap: 28px; + } + + .item-thumbnail { + border-radius: 15px; + width: 140px; + height: 220px; + background-size: contain; + } + + header { + min-width: 120px; + padding: 0 5px; + } + + header .search-box > input { + width: 150px; + } +} + +#goToTop { + position: fixed; + right: 20px; + bottom: 20px; + text-align: center; + + width: 50px; + height: 50px; + border: none; + border-radius: 25px; + + background-color: var(--red); +} + +#goToTop:hover { + box-shadow: 0px 0px 8px 4px rgba(255, 255, 255, 0.4); + cursor: pointer; +} diff --git a/src/css/modal.css b/src/css/modal.css new file mode 100644 index 000000000..53a6ee06b --- /dev/null +++ b/src/css/modal.css @@ -0,0 +1,365 @@ +.modal { + display: none; + position: absolute; + top: 0; + left: 0; +} + +.modal--open { + display: block; +} + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + + background: rgba(0, 0, 0, 0.6); + + z-index: 100; +} + +.modal-backdrop:hover { + cursor: pointer; +} + +.modal-container { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + gap: 0px; + width: 740px; + height: 544px; + + border-radius: 8px; + background-color: rgba(33, 33, 34, 1); + + z-index: 200; +} + +.modal-header { + display: flex; + justify-content: center; + align-items: center; + width: 740px; + height: 60px; + + border-bottom: 1px solid rgba(241, 241, 241, 0.25); +} + +.detail-title { + top: 18px; + left: 32px; + gap: 0px; + + width: 676px; + height: 24px; +} + +.modal-close-button { + width: 36px; + height: 36px; + + border: none; + background-color: transparent; + background-image: url('../images/modal_close_button.png'); +} + +.modal-body { + display: flex; + justify-content: space-between; + align-items: center; + padding: 40px 32px; + gap: 32px; + + width: 740px; + height: 484px; +} + +.detail-poster { + position: fixed; + top: 96px; + left: 32px; + gap: 0px; + + width: 260px; + height: 400px; +} + +.modal-contents { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + position: fixed; + top: 96px; + left: 324px; + + width: 385px; + height: 400px; +} + +.detail-text-container { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 16px; + margin-bottom: auto; + margin-top: 0; + + width: 100%; +} + +.detail-text-top { + display: flex; + align-items: flex-start; + gap: 16px; + + width: 100%; +} + +.detail-genres { +} + +.detail-vote_average { + display: flex; + align-items: center; + gap: 5px; +} + +.detail-overview { + width: 100%; + max-height: 280px; + overflow-y: auto; +} + +.my-vote { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: auto; + margin-bottom: 0; + position: relative; + + width: 384px; + height: 64px; + + border-radius: 16px; + background-color: rgba(56, 56, 57, 1); +} + +.my-vote-title { + position: absolute; + left: 18px; +} + +.my-vote-body { + display: flex; + gap: 0; + position: absolute; + left: 80px; + + width: 160px; +} + +.my-vote-body button { + padding: 0; + + border: none; + background-color: transparent; +} + +.my-vote-body img { + width: 32px; + height: 32px; +} + +.my-vote-number { + position: absolute; + left: 250px; +} + +.my-vote-description { + position: absolute; + left: 278px; +} + +.detail-text-top.skeleton { + height: 24px; +} +.detail-overview.skeleton { + height: 240px; +} + +.detail-title.skeleton, +.detail-poster.skeleton, +.detail-text-top.skeleton, +.detail-overview.skeleton, +.my-vote.skeleton { + background: linear-gradient(-90deg, var(--gray), var(--light-gray), var(--gray), var(--light-gray)); + background-size: 400%; + animation: skeleton-animation 5s infinite ease-out; + border-radius: 8px; +} + +.detail-text-container.skeleton::after, +.detail-overview.skeleton::after { + font-size: 0; + content: 'loading'; +} + +@keyframes skeleton-animation { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +@media only screen and (max-width: 767px) { + .modal-container { + position: fixed; + top: auto; + bottom: 0; + left: 50%; + transform: translateX(-50%); + gap: 0px; + width: 100%; + height: 485px; + border-radius: 8px; + background-color: rgba(33, 33, 34, 1); + z-index: 200; + } + + .modal-header { + display: flex; + justify-content: center; + align-items: center; + padding: 0 20px; + width: 100%; + height: 60px; + + border-bottom: 1px solid rgba(241, 241, 241, 0.25); + } + + .detail-title { + top: 18px; + left: 32px; + gap: 0px; + + width: 100%; + height: 24px; + } + + .modal-close-button { + width: 36px; + height: 36px; + + border: none; + background-color: transparent; + background-image: url('../images/modal_close_button.png'); + } + + .modal-body { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + gap: 0px; + + width: 100%; + height: 100%; + height: 420px; + } + + .detail-poster { + display: none; + width: 0px; + height: 0px; + } + + .modal-contents { + /* display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + margin-bottom: 36px; + position: fixed; + top: 92px; + left: 20px; + + width: 370px; + height: 232px; */ + + display: flex; + flex-direction: column; + justify-content: normal; + left: 20px; + + width: calc(100% - 40px); + height: calc(100% - 60px); + } + + .detail-text-container { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 16px; + margin-bottom: 16px; + margin-top: 0; + + width: 100%; + } + + .detail-text-top { + display: flex; + align-items: flex-start; + gap: 16px; + } + + .detail-genres { + } + + .detail-vote_average { + } + + .detail-overview { + height: 240px; + overflow-y: auto; + } + + .my-vote { + width: 100%; + margin-top: 0; + } + + .my-vote-title { + } + + .my-vote-body { + } + + .my-vote-body button { + } + + .my-vote-body img { + width: 32px; + height: 32px; + } + + .my-vote-number { + } + + .my-vote-description { + display: none; + } +} diff --git a/src/css/theme.css b/src/css/theme.css index 133e04bd6..1a1f7ba83 100644 --- a/src/css/theme.css +++ b/src/css/theme.css @@ -1,8 +1,42 @@ +/* Color ******************************************/ :root { - /* color */ --white: #fff; --light-gray: #f0f0f0; --gray: #aaa; --black: #222; --red: #f33f3f; } + +/* Typography *************************************/ +.text-detail-title { + font-size: 20px; + font-weight: 600; + line-height: 24px; + letter-spacing: 0.15px; + text-align: center; +} + +.text-detail-contents { + font-size: 16px; + font-weight: 400; + line-height: 24px; + letter-spacing: 0.5px; + text-align: left; + color: rgba(241, 241, 241, 1); +} + +.text-detail-vote { + font-size: 16px; + font-weight: 700; + line-height: 24px; + letter-spacing: 0.5px; + text-align: left; +} + +.text-detail-vote-contents { + font-size: 16px; + font-weight: 400; + line-height: 24px; + letter-spacing: 0.5px; + text-align: center; +} \ No newline at end of file diff --git a/src/custom.d.ts b/src/custom.d.ts new file mode 100644 index 000000000..2c9b5d045 --- /dev/null +++ b/src/custom.d.ts @@ -0,0 +1,5 @@ +declare module '*.png'; +declare module '*.jpg'; +declare module '*.jpeg'; +declare module '*.gif'; +declare module '*.svg'; diff --git a/src/images/modal_close_button.png b/src/images/modal_close_button.png new file mode 100644 index 000000000..cbe1b680f Binary files /dev/null and b/src/images/modal_close_button.png differ diff --git a/src/index.d.ts b/src/index.d.ts index 7f46a994c..6f4beff9f 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -14,3 +14,11 @@ export interface Movie { video: boolean; vote_average: number; } + +export interface ModalContent { + title: string; + posterPath: any; + genres: string; + voteAverage: string; + overview: string; +} diff --git a/src/index.js b/src/index.js index 4cbda8a2a..3e9e43387 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,8 @@ import App from './App'; import './css/reset.css'; import './css/theme.css'; import './css/common.css'; +import './css/modal.css'; + const app = new App(); diff --git a/src/store/API.ts b/src/store/API.ts new file mode 100644 index 000000000..62b9189df --- /dev/null +++ b/src/store/API.ts @@ -0,0 +1,28 @@ +const BASE_URL = 'https://api.themoviedb.org/3'; +const API_KEY = process.env.TOKEN; + +const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: `Bearer ${API_KEY}`, + }, +}; + +export async function fetchPopularMovies(pageCount: number) { + const response = await fetch(`${BASE_URL}/movie/popular?language=ko&page=${pageCount}`, options); + + return response; +} + +export async function fetchSearchMovies(query: string, presentPage: number) { + const response = await fetch(`${BASE_URL}/search/movie?query=${query}&language=ko&page=${presentPage}`, options); + + return response; +} + +export async function fetchMovieDetail(movieId: number) { + const response = await fetch(`${BASE_URL}/movie/${movieId}?language=ko`, options); + + return response; +} diff --git a/src/store/MovieStore.ts b/src/store/MovieStore.ts index 18c38f728..57ba205d5 100644 --- a/src/store/MovieStore.ts +++ b/src/store/MovieStore.ts @@ -1,18 +1,13 @@ import { Movie } from '../index.d'; + +import { fetchPopularMovies } from './API'; + import { ERROR_2XX } from '../constants'; import ErrorRender from '../components/ErrorRender'; -const options = { - method: 'GET', - headers: { - accept: 'application/json', - Authorization: `Bearer ${process.env.TOKEN}`, - }, -}; - class MovieStore { - #moviesData: any[]; + #moviesData: Movie[]; #pageCount: number = 1; @@ -20,10 +15,7 @@ class MovieStore { this.#moviesData = []; } - /* eslint-disable max-lines-per-function */ async getMovies() { - await this.#delay(); // Skeleton UI 확인을 위한 강제 delay - try { const responseData = await this.#fetchMoviesData(); this.#pushNewData(responseData); @@ -33,29 +25,26 @@ class MovieStore { } } - /* eslint-disable max-lines-per-function */ async #fetchMoviesData() { - const response = await fetch( - `https://api.themoviedb.org/3/movie/popular?language=ko&page=${this.#pageCount}`, - options, - ); + const response = await fetchPopularMovies(this.#pageCount); + this.#handleResponseError(response); + + const responseJSON = await response.json(); + this.#handleResponseJSONError(response, responseJSON); + + return responseJSON.results; + } + #handleResponseError(response: Response) { if (!response.ok) { throw new ErrorRender(String(response.status)).renderError(); } + } - const responseJSON = await response.json(); - + #handleResponseJSONError(response: Response, responseJSON: any) { if (String(response.status)[0] === ERROR_2XX && responseJSON.results.length === 0) { throw new ErrorRender(String(response.status)).renderError(); } - - return responseJSON.results; - } - - async #delay() { - const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - await delay(2000); } increasePageCount() { diff --git a/src/store/SearchMovieStore.ts b/src/store/SearchMovieStore.ts index 672a1ffae..4c7663234 100644 --- a/src/store/SearchMovieStore.ts +++ b/src/store/SearchMovieStore.ts @@ -1,18 +1,13 @@ import { Movie } from '../index.d'; + import { ERROR_2XX } from '../constants'; import ErrorRender from '../components/ErrorRender'; -const searchOptions = { - method: 'GET', - headers: { - accept: 'application/json', - Authorization: `Bearer ${process.env.TOKEN}`, - }, -}; +import { fetchSearchMovies } from './API'; class SearchMovieStore { - #searchMoviesData: any[]; + #searchMoviesData: Movie[]; #totalPages: number = 0; @@ -24,10 +19,7 @@ class SearchMovieStore { this.#searchMoviesData = []; } - /* eslint-disable max-lines-per-function */ async searchMovies() { - await this.#delay(); // Skeleton UI 확인을 위한 강제 delay - try { const responseData = await this.#fetchSearchData(); const { results } = responseData; @@ -39,29 +31,26 @@ class SearchMovieStore { } } - /* eslint-disable max-lines-per-function */ async #fetchSearchData() { - const response = await fetch( - `https://api.themoviedb.org/3/search/movie?query=${this.#query}&include_adult=false&language=ko&page=${this.#presentPage}`, - searchOptions, - ); + const response = await fetchSearchMovies(this.#query, this.#presentPage); + this.#handleResponseError(response); + + const responseJSON = await response.json(); + this.#handleResponseJSONError(response, responseJSON); + + return responseJSON; + } + #handleResponseError(response: Response) { if (!response.ok) { throw new ErrorRender(String(response.status)).renderError(); } + } - const responseJSON = await response.json(); - + #handleResponseJSONError(response: Response, responseJSON: any) { if (String(response.status)[0] === ERROR_2XX && responseJSON.results.length === 0) { throw new ErrorRender(String(response.status)).renderError(); } - - return responseJSON; - } - - async #delay() { - const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - await delay(2000); } increasePageCount() {