From d9108cb75ce5f91b78ecf11623d03fee717e3cfa Mon Sep 17 00:00:00 2001 From: Yevgeny Yakushev Date: Thu, 16 Oct 2025 21:23:45 +0300 Subject: [PATCH 1/5] start hw2 --- src/index.html | 1 + src/scripts/index.js | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/scripts/index.js diff --git a/src/index.html b/src/index.html index d70da3d..2fe00ce 100644 --- a/src/index.html +++ b/src/index.html @@ -35,5 +35,6 @@

Каталог фильмов

+ \ No newline at end of file diff --git a/src/scripts/index.js b/src/scripts/index.js new file mode 100644 index 0000000..5be36cc --- /dev/null +++ b/src/scripts/index.js @@ -0,0 +1 @@ +console.log("hello") \ No newline at end of file From 8d9396c879b7dbf8783f7370370e85d6458eae2e Mon Sep 17 00:00:00 2001 From: Yevgeny Yakushev Date: Fri, 17 Oct 2025 23:19:33 +0300 Subject: [PATCH 2/5] implemented modal --- package-lock.json | 199 +---------------------------- package.json | 3 +- posthtml.config.js | 9 -- src/data/movies.js | 248 +++++++++++++++++++++++++++++++++++++ src/data/movies.json | 142 --------------------- src/index.html | 26 ++-- src/scripts/index.js | 27 +++- src/scripts/movie-list.js | 53 ++++++++ src/scripts/movie-modal.js | 98 +++++++++++++++ src/styles/common.css | 38 +++++- src/styles/form.css | 31 +++++ src/styles/movie-list.css | 17 --- src/styles/movie-modal.css | 142 +++++++++++++++++++++ 13 files changed, 640 insertions(+), 393 deletions(-) delete mode 100644 posthtml.config.js create mode 100644 src/data/movies.js delete mode 100644 src/data/movies.json create mode 100644 src/scripts/movie-list.js create mode 100644 src/scripts/movie-modal.js create mode 100644 src/styles/form.css create mode 100644 src/styles/movie-modal.css diff --git a/package-lock.json b/package-lock.json index e1958d5..141fb0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "parcel": "^2.16.0", - "posthtml-expressions": "^1.11.4" + "parcel": "^2.16.0" } }, "node_modules/@lezer/common": { @@ -2306,75 +2305,6 @@ "node": ">=0.10" } }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -2411,19 +2341,6 @@ "dev": true, "license": "ISC" }, - "node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2434,13 +2351,6 @@ "node": ">=6" } }, - "node_modules/fclone": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", - "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", - "dev": true, - "license": "MIT" - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2490,26 +2400,6 @@ "node": ">=8" } }, - "node_modules/htmlparser2": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", - "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.2", - "domutils": "^2.8.0", - "entities": "^3.0.1" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2533,13 +2423,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-json": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", - "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", - "dev": true, - "license": "ISC" - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3055,86 +2938,6 @@ "dev": true, "license": "MIT" }, - "node_modules/posthtml": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", - "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "posthtml-parser": "^0.11.0", - "posthtml-render": "^3.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/posthtml-expressions": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/posthtml-expressions/-/posthtml-expressions-1.11.4.tgz", - "integrity": "sha512-tJI6KhKLcePRO0/i4d01MNXfcaBa2jIu4MuVLixvGwCRzxdY2D7LLm17ijNyQNQu3xOhCffBLtUMju0K64smmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fclone": "^1.0.11", - "posthtml": "^0.16.5", - "posthtml-match-helper": "^1.0.1", - "posthtml-parser": "^0.10.0", - "posthtml-render": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/posthtml-match-helper": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-1.0.4.tgz", - "integrity": "sha512-Tj9orTIBxHdnraCxoEGjoizsFsTGvukzwcuhOjYQGmDG6gTlaRbMrGgi1J+FwKTN8hsCQENHYY0Deqs9a89BVg==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "posthtml": ">=0.5.0" - } - }, - "node_modules/posthtml-parser": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.10.2.tgz", - "integrity": "sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "htmlparser2": "^7.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/posthtml-render": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", - "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-json": "^2.0.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/posthtml/node_modules/posthtml-parser": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", - "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "htmlparser2": "^7.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/react-refresh": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.16.0.tgz", diff --git a/package.json b/package.json index 1758671..9a625f2 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "author": "", "license": "ISC", "devDependencies": { - "parcel": "^2.16.0", - "posthtml-expressions": "^1.11.4" + "parcel": "^2.16.0" } } diff --git a/posthtml.config.js b/posthtml.config.js deleted file mode 100644 index ad90161..0000000 --- a/posthtml.config.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - plugins: { - 'posthtml-expressions': { - locals: { - movies: require('./src/data/movies.json') - } - } - } -}; \ No newline at end of file diff --git a/src/data/movies.js b/src/data/movies.js new file mode 100644 index 0000000..d1f6776 --- /dev/null +++ b/src/data/movies.js @@ -0,0 +1,248 @@ +export default [ + { + "id": 1, + "title": "Аватар", + "subtitle": "Avatar, 2009", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/4adf61aa-3cb7-4381-9245-523971e5b4c8/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/4adf61aa-3cb7-4381-9245-523971e5b4c8/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2009" } + ] + }, + { + "id": 2, + "title": "Интерстеллар", + "subtitle": "Interstellar, 2014", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1600647/430042eb-ee69-4818-aed0-a312400a26bf/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1600647/430042eb-ee69-4818-aed0-a312400a26bf/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2014" }, + { name: "Жанр", value: "фантастика, драма, приключения" }, + { name: "Слоган", value: "«Следующий шаг человечества станет величайшим»" }, + { name: "Режиссер", value: "Кристофер Нолан" }, + { name: "Продюсер", value: "Кристофер Нолан, Линда Обст, Эмма Томас" }, + { name: "Бюджет", value: "$165 000 000" }, + { name: "Время", value: "2 ч 49 мин" } + ] + }, + { + "id": 3, + "title": "Начало", + "subtitle": "Inception, 2010", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1629390/8ab9a119-dd74-44f0-baec-0629797483d7/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1629390/8ab9a119-dd74-44f0-baec-0629797483d7/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2010" } + ] + }, + { + "id": 4, + "title": "Мстители: Война бесконечности", + "subtitle": "Avengers: Infinity War, 2018", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1773646/af92d310-4ae5-4daa-b42c-5bcc380c2e6e/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1773646/af92d310-4ae5-4daa-b42c-5bcc380c2e6e/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2018" } + ] + }, + { + "id": 5, + "title": "Матрица", + "subtitle": "The Matrix, 1999", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4774061/cf1970bc-3f08-4e0e-a095-2fb57c3aa7c6/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/4774061/cf1970bc-3f08-4e0e-a095-2fb57c3aa7c6/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "1999" } + ] + }, + { + "id": 6, + "title": "Мстители: Финал", + "subtitle": "Avengers: Endgame, 2019", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1600647/ae22f153-9715-41bb-adb4-f648b3e16092/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1600647/ae22f153-9715-41bb-adb4-f648b3e16092/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2019" } + ] + }, + { + "id": 7, + "title": "Дюна", + "subtitle": "Dune: Part One, 2021", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4303601/9eb762d6-4cdd-464f-9937-aebf30067acc/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/4303601/9eb762d6-4cdd-464f-9937-aebf30067acc/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2021" } + ] + }, + { + "id": 8, + "title": "Пятый элемент", + "subtitle": "The Fifth Element, 1997", + "details": "Франция • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1629390/9e9e2b2c-a3c1-462e-8d84-e6a19fbe5b9c/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1629390/9e9e2b2c-a3c1-462e-8d84-e6a19fbe5b9c/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "1997" } + ] + }, + { + "id": 9, + "title": "Стражи Галактики", + "subtitle": "Guardians of the Galaxy, 2014", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1773646/2e6ab20b-7cf1-49e7-b465-bd5a71c13fa3/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1773646/2e6ab20b-7cf1-49e7-b465-bd5a71c13fa3/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2014" } + ] + }, + { + "id": 10, + "title": "Кракен", + "subtitle": "2025", + "details": "Россия • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/10809116/b722ab4d-497b-4a62-b243-95ca989401ff/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/10809116/b722ab4d-497b-4a62-b243-95ca989401ff/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2025" } + ] + }, + { + "id": 11, + "title": "Темный рыцарь", + "subtitle": "The Dark Knight, 2008", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/0fa5bf50-d5ad-446f-a599-b26d070c8b99/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/0fa5bf50-d5ad-446f-a599-b26d070c8b99/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2008" } + ] + }, + { + "id": 12, + "title": "Доктор Стрэндж", + "subtitle": "Doctor Strange, 2016", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4303601/bb966b79-5b10-485d-88d7-fb6aeb79b185/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/4303601/bb966b79-5b10-485d-88d7-fb6aeb79b185/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2016" } + ] + }, + { + "id": 13, + "title": "Мстители", + "subtitle": "The Avengers, 2012", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1898899/972b7f43-9677-40ce-a9bc-02a88ad3919d/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1898899/972b7f43-9677-40ce-a9bc-02a88ad3919d/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2012" } + ] + }, + { + "id": 14, + "title": "Терминатор 2: Судный день", + "subtitle": "Terminator 2: Judgment Day, 1991", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/10893610/2dd14742-f241-42ca-9db4-331e3a483c50/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/10893610/2dd14742-f241-42ca-9db4-331e3a483c50/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "1991" } + ] + }, + { + "id": 15, + "title": "Железный человек", + "subtitle": "Iron Man, 2008", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4774061/c8e2f069-15f1-4803-95c0-aba858fec360/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/4774061/c8e2f069-15f1-4803-95c0-aba858fec360/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2008" } + ] + }, + { + "id": 16, + "title": "Назад в будущее", + "subtitle": "Back to the Future, 1985", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/73cf2ed0-fd52-47a2-9e26-74104360786a/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/73cf2ed0-fd52-47a2-9e26-74104360786a/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "1985" } + ] + }, + { + "id": 17, + "title": "Марсианин", + "subtitle": "The Martian, 2015", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1900788/6f631486-e947-487d-94d6-41c2b5a8f5a0/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1900788/6f631486-e947-487d-94d6-41c2b5a8f5a0/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2015" } + ] + }, + { + "id": 18, + "title": "Кибердеревня", + "subtitle": "2023", + "details": "Россия • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/9784475/70c75cf3-f456-4474-a900-9a38c1bb2987/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/9784475/70c75cf3-f456-4474-a900-9a38c1bb2987/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2023" } + ] + }, + { + "id": 19, + "title": "Главный герой", + "subtitle": "Free Guy, 2021", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/6201401/db4fbef1-466a-4dec-9b7a-d4f13eb45738/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/6201401/db4fbef1-466a-4dec-9b7a-d4f13eb45738/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2021" } + ] + }, + { + "id": 20, + "title": "Первому игроку приготовиться", + "subtitle": "Ready Player One, 2018", + "details": "США • фантастика", + "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1946459/5ae82f4b-fd6a-46b5-b5ba-897106eb1eae/136x204", + "imgBig": "https://avatars.mds.yandex.net/get-kinopoisk-image/1946459/5ae82f4b-fd6a-46b5-b5ba-897106eb1eae/600x900", + "link": "#", + "metadata": [ + { name: "Год производства", value: "2018" } + ] + } +] \ No newline at end of file diff --git a/src/data/movies.json b/src/data/movies.json deleted file mode 100644 index 4f1f5f2..0000000 --- a/src/data/movies.json +++ /dev/null @@ -1,142 +0,0 @@ -[ - { - "title": "Аватар", - "subtitle": "Avatar, 2009", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/4adf61aa-3cb7-4381-9245-523971e5b4c8/136x204", - "link": "#" - }, - { - "title": "Интерстеллар", - "subtitle": "Interstellar, 2014", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1600647/430042eb-ee69-4818-aed0-a312400a26bf/136x204", - "link": "#" - }, - { - "title": "Начало", - "subtitle": "Inception, 2010", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1629390/8ab9a119-dd74-44f0-baec-0629797483d7/136x204", - "link": "#" - }, - { - "title": "Мстители: Война бесконечности", - "subtitle": "Avengers: Infinity War, 2018", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1773646/af92d310-4ae5-4daa-b42c-5bcc380c2e6e/136x204", - "link": "#" - }, - { - "title": "Матрица", - "subtitle": "The Matrix, 1999", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4774061/cf1970bc-3f08-4e0e-a095-2fb57c3aa7c6/136x204", - "link": "#" - }, - { - "title": "Мстители: Финал", - "subtitle": "Avengers: Endgame, 2019", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1600647/ae22f153-9715-41bb-adb4-f648b3e16092/136x204", - "link": "#" - }, - { - "title": "Дюна", - "subtitle": "Dune: Part One, 2021", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4303601/9eb762d6-4cdd-464f-9937-aebf30067acc/136x204", - "link": "#" - }, - { - "title": "Пятый элемент", - "subtitle": "The Fifth Element, 1997", - "details": "Франция • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1629390/9e9e2b2c-a3c1-462e-8d84-e6a19fbe5b9c/136x204", - "link": "#" - }, - { - "title": "Стражи Галактики", - "subtitle": "Guardians of the Galaxy, 2014", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1773646/2e6ab20b-7cf1-49e7-b465-bd5a71c13fa3/136x204", - "link": "#" - }, - { - "title": "Кракен", - "subtitle": "2025", - "details": "Россия • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/10809116/b722ab4d-497b-4a62-b243-95ca989401ff/136x204", - "link": "#" - }, - { - "title": "Темный рыцарь", - "subtitle": "The Dark Knight, 2008", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/0fa5bf50-d5ad-446f-a599-b26d070c8b99/136x204", - "link": "#" - }, - { - "title": "Доктор Стрэндж", - "subtitle": "Doctor Strange, 2016", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4303601/bb966b79-5b10-485d-88d7-fb6aeb79b185/136x204", - "link": "#" - }, - { - "title": "Мстители", - "subtitle": "The Avengers, 2012", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1898899/972b7f43-9677-40ce-a9bc-02a88ad3919d/136x204", - "link": "#" - }, - { - "title": "Терминатор 2: Судный день", - "subtitle": "Terminator 2: Judgment Day, 1991", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/10893610/2dd14742-f241-42ca-9db4-331e3a483c50/136x204", - "link": "#" - }, - { - "title": "Железный человек", - "subtitle": "Iron Man, 2008", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/4774061/c8e2f069-15f1-4803-95c0-aba858fec360/136x204", - "link": "#" - }, - { - "title": "Назад в будущее", - "subtitle": "Back to the Future, 1985", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1599028/73cf2ed0-fd52-47a2-9e26-74104360786a/136x204", - "link": "#" - }, - { - "title": "Марсианин", - "subtitle": "The Martian, 2015", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1900788/6f631486-e947-487d-94d6-41c2b5a8f5a0/136x204", - "link": "#" - }, - { - "title": "Кибердеревня", - "subtitle": "2023", - "details": "Россия • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/9784475/70c75cf3-f456-4474-a900-9a38c1bb2987/136x204", - "link": "#" - }, - { - "title": "Главный герой", - "subtitle": "Free Guy, 2021", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/6201401/db4fbef1-466a-4dec-9b7a-d4f13eb45738/136x204", - "link": "#" - }, - { - "title": "Первому игроку приготовиться", - "subtitle": "Ready Player One, 2018", - "details": "США • фантастика", - "img": "https://avatars.mds.yandex.net/get-kinopoisk-image/1946459/5ae82f4b-fd6a-46b5-b5ba-897106eb1eae/136x204", - "link": "#" - } -] \ No newline at end of file diff --git a/src/index.html b/src/index.html index 2fe00ce..d441d1c 100644 --- a/src/index.html +++ b/src/index.html @@ -1,5 +1,6 @@ + @@ -8,33 +9,20 @@ + + +

Каталог фильмов

-
-
- - - - -
-
+
+ + \ No newline at end of file diff --git a/src/scripts/index.js b/src/scripts/index.js index 5be36cc..35cbecf 100644 --- a/src/scripts/index.js +++ b/src/scripts/index.js @@ -1 +1,26 @@ -console.log("hello") \ No newline at end of file +import MovieList from "./movie-list"; +import MovieModal from "./movie-modal"; +import movies from "../data/movies"; + +class IndexPage { + movieList + movieModal + + constructor() { + this.#init(); + } + + #init() { + this.movieList = new MovieList("#movie-list-container", movies); + this.movieModal = new MovieModal("#movie-modal"); + } + + render() { + this.movieList.render(); + } +} + +document.addEventListener("DOMContentLoaded", () => { + window.indexPage = new IndexPage(); + window.indexPage.render(); +}); \ No newline at end of file diff --git a/src/scripts/movie-list.js b/src/scripts/movie-list.js new file mode 100644 index 0000000..e6f55f9 --- /dev/null +++ b/src/scripts/movie-list.js @@ -0,0 +1,53 @@ +export default class MovieList { + #$el + #data + + constructor(selector, initialData = []) { + this.#$el = document.querySelector(selector); + this.#data = initialData; + } + + #createItem({ id, link, title, subtitle, img, details }) { + return ` + + `; + } + + #createList() { + return ` +
+ ${this.#data.map(this.#createItem.bind(this)).join("")} +
+ `; + } + + #attachEvents() { + const movieList = document.querySelector(".movie-list"); + movieList.addEventListener("click", (event) => { + event.preventDefault(); + const movieItem = event.target.closest("[data-modal-open]"); + if (movieItem) { + const movieId = +movieItem.dataset.id; + window.indexPage.movieModal.open(movieId); + } + }); + } + + render() { + const movieListHTML = this.#createList(); + this.#$el.replaceChildren(); + this.#$el.innerHTML = movieListHTML; + this.#attachEvents(); + } +} \ No newline at end of file diff --git a/src/scripts/movie-modal.js b/src/scripts/movie-modal.js new file mode 100644 index 0000000..e49b4f5 --- /dev/null +++ b/src/scripts/movie-modal.js @@ -0,0 +1,98 @@ +import movies from "../data/movies"; + +export default class MovieModal { + #$el + #data + + constructor(selector) { + this.#$el = document.querySelector(selector); + } + + open(movieId) { + this.#fetchData(movieId); + this.render(); + this.#$el.showModal(); + } + + close() { + this.#$el.close(); + } + + #fetchData(movieId) { + this.#data = movies.find(({ id }) => id === movieId); + } + + #createMetadataList() { + if (!this.#data.metadata || !this.#data.metadata.length) { + return "Нет информации"; + } + + return ` + + `; + } + + #createModal() { + return ` +
+
+

Выбор фильма

+ +
+
+
+ ${this.#data.title} +
+
+
+

${this.#data.title}

+ ${this.#createMetadataList()} +
+ + + Смотреть вместе → + +
+
+
+ `; + } + + #showSettings() { + alert("Settings are not implemented"); + } + + #navigateToWatchPage(movieId) { + alert("Watch page is not implemented"); + } + + #attachEvents() { + const closeButtons = this.#$el.querySelectorAll("[data-modal-close]"); + closeButtons.forEach(btn => btn.addEventListener("click", () => this.close())); + + const settingsButton = this.#$el.querySelector(".movie-modal-settings-btn"); + settingsButton.addEventListener("click", (event) => { + event.preventDefault(); + this.#showSettings(); + }); + + const watchButton = this.#$el.querySelector(".movie-modal-watch-btn"); + watchButton.addEventListener("click", (event) => { + event.preventDefault(); + this.#navigateToWatchPage(this.#data.id); + }); + } + + render() { + const modalHTML = this.#createModal(); + this.#$el.replaceChildren(); + this.#$el.innerHTML = modalHTML; + this.#attachEvents(); + } +} \ No newline at end of file diff --git a/src/styles/common.css b/src/styles/common.css index 27361d8..17eaea2 100644 --- a/src/styles/common.css +++ b/src/styles/common.css @@ -4,6 +4,10 @@ --color-h1: #FFFFFF; --color-base: #E0E0E0; --color-base2: #A0A0A0; + --color-primary: #4A90E2; + --color-primary-hover: #357ABD; + --color-secondary: #F4B400; + --color-secondary-hover: #D99A00; --spacing: .25rem; @@ -14,6 +18,7 @@ --text-h2: clamp(16px, 3vw, 21px); --text-h3: clamp(14px, 2.5vw, 17px); --text-base: clamp(12px, 2vw, 14px); + --text-button: clamp(13px, 2vw, 15px); } *, @@ -27,10 +32,38 @@ body { font-family: Inter, sans-serif; + font-size: var(--text-base); + color: var(--color-base); line-height: 1.5; background-color: var(--color-bg); } +body:has(dialog[open]) { + overflow: hidden; +} + +p { + font-size: var(--text-base); + color: var(--color-base); +} + +h1 { + font-size: var(--text-h1); + color: var(--color-h1); +} + +h2 { + font-weight: var(--font-weight-bold); + font-size: var(--text-h2); + color: var(--color-base); +} + +h3 { + font-weight: var(--font-weight-semibold); + font-size: var(--text-h3); + color: var(--color-base2); +} + /* header */ .main-header { position: sticky; @@ -39,9 +72,4 @@ body { padding-block: calc(var(--spacing) * 6) calc(var(--spacing) * 2); padding-inline: calc(var(--spacing) * 6); box-shadow: 6px 10px 10px 0 color-mix(in srgb, var(--color-bg) 50%, transparent); -} - -.main-header h1 { - font-size: var(--text-h1); - color: var(--color-h1); } \ No newline at end of file diff --git a/src/styles/form.css b/src/styles/form.css new file mode 100644 index 0000000..ddc774d --- /dev/null +++ b/src/styles/form.css @@ -0,0 +1,31 @@ +.primary-btn, +.secondary-btn { + height: 45px; + min-width: 185px; + font-size: var(--text-button); + font-weight: var(--font-weight-bold); + display: flex; + align-items: center; + justify-content: center; + border: none; + text-decoration: none; + cursor: pointer; +} + +.primary-btn { + background-color: var(--color-primary); + color: var(--color-h1); +} + +.primary-btn:hover { + background-color: var(--color-primary-hover); +} + +.secondary-btn { + background-color: var(--color-secondary); + color: var(--color-bg); +} + +.secondary-btn:hover { + background-color: var(--color-secondary-hover); +} \ No newline at end of file diff --git a/src/styles/movie-list.css b/src/styles/movie-list.css index e7ad8f9..dc9f170 100644 --- a/src/styles/movie-list.css +++ b/src/styles/movie-list.css @@ -41,23 +41,6 @@ gap: 0.45rem; } -.movie-item-title { - font-weight: var(--font-weight-bold); - font-size: var(--text-h2); - color: var(--color-base); -} - -.movie-item-subtitle { - font-weight: var(--font-weight-semibold); - font-size: var(--text-h3); - color: var(--color-base2); -} - -.movie-item-details { - font-size: var(--text-base); - color: var(--color-base); -} - @media (max-width: 1024px) { .movie-list { grid-template-columns: repeat(2, minmax(0, 1fr)); diff --git a/src/styles/movie-modal.css b/src/styles/movie-modal.css new file mode 100644 index 0000000..85cbd1e --- /dev/null +++ b/src/styles/movie-modal.css @@ -0,0 +1,142 @@ +dialog { + align-self: center; + justify-self: center; + border: none; + padding: 0; + max-width: 860px; + width: 90%; + background-color: var(--color-bg); + color: var(--color-base); + box-shadow: 6px 10px 10px 0 color-mix(in srgb, var(--color-bg) 50%, transparent); +} + +dialog::backdrop { + background: rgba(0, 0, 0, 0.5); +} + +.movie-modal-header { + display: flex; + align-items: center; + height: 70px; + padding-inline: calc(var(--spacing) * 5); +} + +.movie-modal-header h3 { + flex-grow: 1; +} + +.movie-modal-close-btn { + background: none; + border: none; + font-weight: var(--font-weight-bold); + font-size: var(--text-h2); + color: var(--color-base); + cursor: pointer; +} + +.movie-modal-poster img { + width: 400px; + object-fit: cover; +} + +.movie-modal-body { + display: flex; +} + +.movie-modal-content { + display: flex; + flex-direction: column; + flex-grow: 1; + min-width: 0; + padding-inline: calc(var(--spacing) * 6); + padding-block-end: calc(var(--spacing) * 6); +} + +.movie-modal-content>div { + flex-grow: 1; + display: flex; + flex-direction: column; + gap: calc(var(--spacing) * 3); +} + +.movie-modal-content ul { + list-style: none; + display: flex; + flex-direction: column; + gap: calc(var(--spacing) * 2); +} + +.movie-modal-content li { + display: flex; +} + +.movie-modal-content li>span:nth-child(1) { + width: 160px; + flex: 0 0 auto; +} + +.movie-modal-content menu { + display: flex; + justify-content: center; + gap: calc(var(--spacing) * 7); +} + +@media (max-width: 1024px) { + .movie-modal-header { + height: 50px; + padding-inline: calc(var(--spacing) * 4); + } + + .movie-modal-content { + padding-block-end: calc(var(--spacing) * 2); + } + + .movie-modal-content li>span:nth-child(1) { + width: 140px; + } + + .movie-modal-content menu { + flex-direction: column; + gap: calc(var(--spacing) * 4); + } +} + +@media (max-width: 600px) { + dialog { + width: 100vw; + height: 100vh; + max-height: 100vh; + overflow: hidden; + } + + .movie-modal-header { + padding-inline: calc(var(--spacing) * 4); + position: relative; + width: 100vw; + background-color: var(--color-bg); + box-shadow: 6px 10px 10px 0 color-mix(in srgb, var(--color-bg) 50%, transparent); + } + + .movie-modal-body { + flex-direction: column; + overflow-y: auto; + height: calc(100vh - 45px - var(--spacing) * 3); + } + + .movie-modal-content>div { + padding-block: calc(var(--spacing) * 3) calc(var(--spacing) * 10); + } + + .movie-modal-poster img { + object-fit: cover; + width: 100%; + } + + .movie-modal-content menu { + position: fixed; + bottom: calc(var(--spacing) * 3); + flex-direction: row; + left: 0; + width: 100vw; + } +} \ No newline at end of file From 7e56adb7962a4baf9eb4e1af310cabc03f5d12f1 Mon Sep 17 00:00:00 2001 From: Yevgeny Yakushev Date: Sat, 18 Oct 2025 01:16:05 +0300 Subject: [PATCH 3/5] refactored baseview --- src/scripts/index.js | 18 ++--- src/scripts/models/movie-model.js | 11 +++ src/scripts/movie-list.js | 53 -------------- src/scripts/views/base-view.js | 31 ++++++++ src/scripts/views/movie-list-view.js | 60 ++++++++++++++++ .../movie-modal-view.js} | 72 +++++++++---------- 6 files changed, 144 insertions(+), 101 deletions(-) create mode 100644 src/scripts/models/movie-model.js delete mode 100644 src/scripts/movie-list.js create mode 100644 src/scripts/views/base-view.js create mode 100644 src/scripts/views/movie-list-view.js rename src/scripts/{movie-modal.js => views/movie-modal-view.js} (53%) diff --git a/src/scripts/index.js b/src/scripts/index.js index 35cbecf..4d1f070 100644 --- a/src/scripts/index.js +++ b/src/scripts/index.js @@ -1,22 +1,16 @@ -import MovieList from "./movie-list"; -import MovieModal from "./movie-modal"; -import movies from "../data/movies"; +import MovieListView from "./views/movie-list-view"; +import MovieModel from "./models/movie-model"; class IndexPage { - movieList - movieModal + movieListView constructor() { - this.#init(); - } - - #init() { - this.movieList = new MovieList("#movie-list-container", movies); - this.movieModal = new MovieModal("#movie-modal"); + const movies = MovieModel.getAll(); + this.movieListView = new MovieListView("#movie-list-container", movies); } render() { - this.movieList.render(); + this.movieListView.render(); } } diff --git a/src/scripts/models/movie-model.js b/src/scripts/models/movie-model.js new file mode 100644 index 0000000..572e693 --- /dev/null +++ b/src/scripts/models/movie-model.js @@ -0,0 +1,11 @@ +import movies from "../../data/movies.js"; + +export default class MovieModel { + static getAll() { + return movies; + } + + static getById(id) { + return movies.find(movie => movie.id === id); + } +} \ No newline at end of file diff --git a/src/scripts/movie-list.js b/src/scripts/movie-list.js deleted file mode 100644 index e6f55f9..0000000 --- a/src/scripts/movie-list.js +++ /dev/null @@ -1,53 +0,0 @@ -export default class MovieList { - #$el - #data - - constructor(selector, initialData = []) { - this.#$el = document.querySelector(selector); - this.#data = initialData; - } - - #createItem({ id, link, title, subtitle, img, details }) { - return ` - - `; - } - - #createList() { - return ` -
- ${this.#data.map(this.#createItem.bind(this)).join("")} -
- `; - } - - #attachEvents() { - const movieList = document.querySelector(".movie-list"); - movieList.addEventListener("click", (event) => { - event.preventDefault(); - const movieItem = event.target.closest("[data-modal-open]"); - if (movieItem) { - const movieId = +movieItem.dataset.id; - window.indexPage.movieModal.open(movieId); - } - }); - } - - render() { - const movieListHTML = this.#createList(); - this.#$el.replaceChildren(); - this.#$el.innerHTML = movieListHTML; - this.#attachEvents(); - } -} \ No newline at end of file diff --git a/src/scripts/views/base-view.js b/src/scripts/views/base-view.js new file mode 100644 index 0000000..2bb53f3 --- /dev/null +++ b/src/scripts/views/base-view.js @@ -0,0 +1,31 @@ +// Базовый класс для View-компонентов – +// содержит общие методы рендера и работы с событиями + +export default class BaseView { + _$el + _data + + constructor(selector, initialData = {}) { + this._$el = document.querySelector(selector); + this._data = initialData; + } + + _createInnerHTML() { + throw new Error("Method _createInnerHTML() must be implemented"); + } + + _detachEvents() { + throw new Error("Method _detachEvents() must be implemented"); + } + + _attachEvents() { + throw new Error("Method _attachEvents() must be implemented"); + } + + render() { + this._detachEvents(); + const innerHTML = this._createInnerHTML(); + this._$el.innerHTML = innerHTML; + this._attachEvents(); + } +} \ No newline at end of file diff --git a/src/scripts/views/movie-list-view.js b/src/scripts/views/movie-list-view.js new file mode 100644 index 0000000..3a50ff9 --- /dev/null +++ b/src/scripts/views/movie-list-view.js @@ -0,0 +1,60 @@ + +import BaseView from "./base-view"; +import { createMovieModal } from "./movie-modal-view"; +import MovieModel from "../models/movie-model"; + +export default class MovieListView extends BaseView { + #movieModal + + constructor(selector, initialData = []) { + super(selector, initialData); + this.#movieModal = createMovieModal(); + } + + #createItem({ id, link, title, subtitle, img, details }) { + return ` + + `; + } + + #createList() { + return ` +
+ ${this._data.map(this.#createItem.bind(this)).join("")} +
+ `; + } + + _createInnerHTML() { + return this.#createList(); + } + + #handleModal = (event) => { + event.preventDefault(); + const movieItem = event.target.closest("[data-modal-open]"); + if (movieItem) { + const movieId = +movieItem.dataset.id; + const movie = MovieModel.getById(movieId); + this.#movieModal.open(movie); + } + } + + _detachEvents() { + this._$el.removeEventListener("click", this.#handleModal); + } + + _attachEvents() { + this._$el.addEventListener("click", this.#handleModal); + } +} \ No newline at end of file diff --git a/src/scripts/movie-modal.js b/src/scripts/views/movie-modal-view.js similarity index 53% rename from src/scripts/movie-modal.js rename to src/scripts/views/movie-modal-view.js index e49b4f5..e55aaf1 100644 --- a/src/scripts/movie-modal.js +++ b/src/scripts/views/movie-modal-view.js @@ -1,35 +1,28 @@ -import movies from "../data/movies"; +import BaseView from "./base-view"; -export default class MovieModal { - #$el - #data - - constructor(selector) { - this.#$el = document.querySelector(selector); +export default class MovieModalView extends BaseView { + constructor(selector, initialData = {}) { + super(selector, initialData); } - open(movieId) { - this.#fetchData(movieId); + open(movie) { + this._data = movie; this.render(); - this.#$el.showModal(); + this._$el.showModal(); } close() { - this.#$el.close(); - } - - #fetchData(movieId) { - this.#data = movies.find(({ id }) => id === movieId); + this._$el.close(); } #createMetadataList() { - if (!this.#data.metadata || !this.#data.metadata.length) { + if (!this._data.metadata || !this._data.metadata.length) { return "Нет информации"; } return `
    - ${this.#data.metadata.map(({ name, value }) => ` + ${this._data.metadata.map(({ name, value }) => `
  • ${name}:${value}
  • @@ -47,16 +40,16 @@ export default class MovieModal {
    - ${this.#data.title} + ${this._data.title}
    -

    ${this.#data.title}

    +

    ${this._data.title}

    ${this.#createMetadataList()}
    - Смотреть вместе → + Смотреть вместе →
    @@ -64,6 +57,10 @@ export default class MovieModal { `; } + _createInnerHTML() { + return this.#createModal(); + } + #showSettings() { alert("Settings are not implemented"); } @@ -72,27 +69,30 @@ export default class MovieModal { alert("Watch page is not implemented"); } - #attachEvents() { - const closeButtons = this.#$el.querySelectorAll("[data-modal-close]"); - closeButtons.forEach(btn => btn.addEventListener("click", () => this.close())); + #handleButtons = (event) => { + const closeBtn = event.target.closest("[data-modal-close]"); + if (closeBtn) return this.close(); - const settingsButton = this.#$el.querySelector(".movie-modal-settings-btn"); - settingsButton.addEventListener("click", (event) => { + const settingsBtn = event.target.closest(".movie-modal-settings-btn"); + if (settingsBtn) { event.preventDefault(); this.#showSettings(); - }); + } - const watchButton = this.#$el.querySelector(".movie-modal-watch-btn"); - watchButton.addEventListener("click", (event) => { + const watchBtn = event.target.closest(".movie-modal-watch-btn"); + if (watchBtn) { event.preventDefault(); - this.#navigateToWatchPage(this.#data.id); - }); + this.#navigateToWatchPage(this._data.id); + } } - render() { - const modalHTML = this.#createModal(); - this.#$el.replaceChildren(); - this.#$el.innerHTML = modalHTML; - this.#attachEvents(); + _detachEvents() { + this._$el.removeEventListener("click", this.#handleButtons); } -} \ No newline at end of file + + _attachEvents() { + this._$el.addEventListener("click", this.#handleButtons); + } +} + +export const createMovieModal = () => new MovieModalView("#movie-modal"); \ No newline at end of file From 425009afd2a8ab9df1babd8f81464bbf3c2114f1 Mon Sep 17 00:00:00 2001 From: Yevgeny Yakushev Date: Sat, 18 Oct 2025 01:16:40 +0300 Subject: [PATCH 4/5] added jsdocs --- src/scripts/index.js | 13 ++++++++++ src/scripts/models/movie-model.js | 13 ++++++++++ src/scripts/views/base-view.js | 35 ++++++++++++++++++++++++--- src/scripts/views/movie-list-view.js | 34 +++++++++++++++++++++++++- src/scripts/views/movie-modal-view.js | 35 +++++++++++++++++++++++++-- 5 files changed, 124 insertions(+), 6 deletions(-) diff --git a/src/scripts/index.js b/src/scripts/index.js index 4d1f070..7bc0ed0 100644 --- a/src/scripts/index.js +++ b/src/scripts/index.js @@ -1,19 +1,32 @@ import MovieListView from "./views/movie-list-view"; import MovieModel from "./models/movie-model"; +/** + * Класс главной страницы приложения. + * Отвечает за инициализацию и рендер списка фильмов. + */ class IndexPage { + /** @type {MovieListView} Список фильмов */ movieListView constructor() { + // Получаем все фильмы из модели const movies = MovieModel.getAll(); + // Создаём View для списка фильмов this.movieListView = new MovieListView("#movie-list-container", movies); } + /** Рендерит главную страницу */ render() { this.movieListView.render(); } } +/** + * Инициализация приложения после полной загрузки DOM + * - чтобы все селекторы уже существовали + * - window.indexPage даёт доступ к корневому объекту в консоли браузера (для отладки) + */ document.addEventListener("DOMContentLoaded", () => { window.indexPage = new IndexPage(); window.indexPage.render(); diff --git a/src/scripts/models/movie-model.js b/src/scripts/models/movie-model.js index 572e693..34adde3 100644 --- a/src/scripts/models/movie-model.js +++ b/src/scripts/models/movie-model.js @@ -1,10 +1,23 @@ import movies from "../../data/movies.js"; +/** + * Класс модели фильмов. + * Отвечает за работу с данными о фильмах. + */ export default class MovieModel { + /** + * Возвращает все фильмы + * @returns {Array} Массив объектов фильмов + */ static getAll() { return movies; } + /** + * Возвращает фильм по его идентификатору + * @param {number} id - Идентификатор фильма + * @returns {Object|undefined} Объект фильма или undefined, если не найден + */ static getById(id) { return movies.find(movie => movie.id === id); } diff --git a/src/scripts/views/base-view.js b/src/scripts/views/base-view.js index 2bb53f3..a872a30 100644 --- a/src/scripts/views/base-view.js +++ b/src/scripts/views/base-view.js @@ -1,27 +1,56 @@ -// Базовый класс для View-компонентов – -// содержит общие методы рендера и работы с событиями - +/** + * Базовый класс для всех View-компонентов. + * Содержит общие методы рендера и управления событиями. + * + * Примечание: методы _createInnerHTML, _attachEvents и _detachEvents + * должны быть реализованы в наследниках, иначе будет ошибка. + */ export default class BaseView { + /** @type {HTMLElement} Элемент контейнера, куда рендерится контент */ _$el + + /** @type {any} Данные для отображения в компоненте */ _data + /** + * @param {string} selector - CSS-селектор контейнера в DOM + * @param {any} initialData - Начальные данные для компонента + */ constructor(selector, initialData = {}) { this._$el = document.querySelector(selector); this._data = initialData; } + /** + * Метод должен вернуть HTML-контент компонента. + * Должен быть реализован в наследнике. + * @returns {string} HTML-код компонента + */ _createInnerHTML() { throw new Error("Method _createInnerHTML() must be implemented"); } + /** + * Метод для удаления событий перед перерендером. + * Должен быть реализован в наследнике. + */ _detachEvents() { throw new Error("Method _detachEvents() must be implemented"); } + /** + * Метод для установки событий после рендера. + * Должен быть реализован в наследнике. + */ _attachEvents() { throw new Error("Method _attachEvents() must be implemented"); } + /** + * Основной метод рендера компонента. + * Сначала удаляет старые события, затем создаёт HTML, + * затем вешает новые события. + */ render() { this._detachEvents(); const innerHTML = this._createInnerHTML(); diff --git a/src/scripts/views/movie-list-view.js b/src/scripts/views/movie-list-view.js index 3a50ff9..11601c2 100644 --- a/src/scripts/views/movie-list-view.js +++ b/src/scripts/views/movie-list-view.js @@ -3,14 +3,29 @@ import BaseView from "./base-view"; import { createMovieModal } from "./movie-modal-view"; import MovieModel from "../models/movie-model"; +/** + * Класс для отображения списка фильмов. + * Рендерит карточки фильмов и обрабатывает открытие модалки при клике. + * Наследуется от BaseView. + */ export default class MovieListView extends BaseView { - #movieModal + /** @type {MovieModalView} Экземпляр модального окна */ + #movieModal; + /** + * @param {string} selector - CSS-селектор контейнера списка фильмов + * @param {Array} initialData - Массив объектов фильмов + */ constructor(selector, initialData = []) { super(selector, initialData); this.#movieModal = createMovieModal(); } + /** + * Генерирует HTML для одной карточки фильма + * @param {Object} movie - Объект фильма + * @returns {string} HTML-код карточки + */ #createItem({ id, link, title, subtitle, img, details }) { return `
    @@ -28,6 +43,10 @@ export default class MovieListView extends BaseView { `; } + /** + * Генерирует HTML для всего списка фильмов + * @returns {string} HTML-код списка фильмов + */ #createList() { return `
    @@ -36,12 +55,23 @@ export default class MovieListView extends BaseView { `; } + /** Метод рендера HTML (обязательный для BaseView) */ _createInnerHTML() { return this.#createList(); } + /** + * Обработчик клика на карточке фильма. + * Делегирование: ищет ближайший элемент с data-modal-open + * и открывает соответствующую модалку. + * @param {MouseEvent} event + */ #handleModal = (event) => { event.preventDefault(); + + // Используем event.target.closest('[data-modal-open]') + // чтобы найти ближайший элемент с атрибутом data-modal-open + // даже если кликнули по вложенному тегу внутри карточки const movieItem = event.target.closest("[data-modal-open]"); if (movieItem) { const movieId = +movieItem.dataset.id; @@ -50,10 +80,12 @@ export default class MovieListView extends BaseView { } } + /** Убирает обработчики событий перед перерендером */ _detachEvents() { this._$el.removeEventListener("click", this.#handleModal); } + /** Добавляет обработчики событий после рендера */ _attachEvents() { this._$el.addEventListener("click", this.#handleModal); } diff --git a/src/scripts/views/movie-modal-view.js b/src/scripts/views/movie-modal-view.js index e55aaf1..7e12f10 100644 --- a/src/scripts/views/movie-modal-view.js +++ b/src/scripts/views/movie-modal-view.js @@ -1,20 +1,38 @@ import BaseView from "./base-view"; +/** + * Класс модального окна фильма. + * Отвечает за отображение деталей фильма и работу с кнопками внутри модалки. + * Наследуется от BaseView. + */ export default class MovieModalView extends BaseView { + /** + * @param {string} selector - CSS-селектор контейнера модалки + * @param {Object} initialData - Начальные данные для модалки + */ constructor(selector, initialData = {}) { super(selector, initialData); } + /** + * Открывает модалку с переданными данными фильма + * @param {Object} movie - Объект фильма + */ open(movie) { this._data = movie; this.render(); - this._$el.showModal(); + this._$el.showModal(); // встроенный метод HTML-элемента } + /** Закрывает модалку */ close() { this._$el.close(); } + /** + * Генерирует HTML для списка метаданных фильма + * @returns {string} HTML-строка с метаданными + */ #createMetadataList() { if (!this._data.metadata || !this._data.metadata.length) { return "Нет информации"; @@ -31,6 +49,7 @@ export default class MovieModalView extends BaseView { `; } + /** Генерирует полный HTML модалки */ #createModal() { return `
    @@ -57,18 +76,25 @@ export default class MovieModalView extends BaseView { `; } + /** Метод для рендера HTML (обязательный для BaseView) */ _createInnerHTML() { return this.#createModal(); } + /** TODO: Показ уведомления о настройках (заглушка) */ #showSettings() { alert("Settings are not implemented"); } + /** TODO: Переход на страницу просмотра фильма (заглушка) */ #navigateToWatchPage(movieId) { alert("Watch page is not implemented"); } + /** + * Обработчик кликов по кнопкам модалки + * @param {MouseEvent} event + */ #handleButtons = (event) => { const closeBtn = event.target.closest("[data-modal-close]"); if (closeBtn) return this.close(); @@ -86,13 +112,18 @@ export default class MovieModalView extends BaseView { } } + /** Убирает события перед перерендером (BaseView) */ _detachEvents() { this._$el.removeEventListener("click", this.#handleButtons); } - _attachEvents() { + /** Добавляет события после рендера (BaseView) */ + _attachEvents() { + // Делегирование событий кликов на контейнер модалки + // Позволяет обрабатывать все кнопки внутри одной функции this._$el.addEventListener("click", this.#handleButtons); } } +/** Фабрика для создания модалки */ export const createMovieModal = () => new MovieModalView("#movie-modal"); \ No newline at end of file From c1da21ff72855b6e4dbd803e5e0071d793cb4d46 Mon Sep 17 00:00:00 2001 From: Yevgeny Yakushev Date: Sat, 18 Oct 2025 02:02:57 +0300 Subject: [PATCH 5/5] fixed mobile --- src/styles/movie-modal.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/movie-modal.css b/src/styles/movie-modal.css index 85cbd1e..0c5e828 100644 --- a/src/styles/movie-modal.css +++ b/src/styles/movie-modal.css @@ -120,7 +120,7 @@ dialog::backdrop { .movie-modal-body { flex-direction: column; overflow-y: auto; - height: calc(100vh - 45px - var(--spacing) * 3); + height: calc(100vh - 120px); } .movie-modal-content>div {