diff --git a/build/bundle.js b/build/bundle.js deleted file mode 100644 index e69de29..0000000 diff --git a/build/css/style.css b/build/css/style.css deleted file mode 100644 index c6c86ec..0000000 --- a/build/css/style.css +++ /dev/null @@ -1,833 +0,0 @@ -@font-face { - font-family: 'Montserrat'; - font-style: normal; - font-weight: 400; - src: local('Montserrat Regular'), local('Montserrat-Regular'), url('../fonts/Montserrat-Regular.woff2') format('woff2'); -} - -@font-face { - font-family: 'Montserrat'; - font-style: normal; - font-weight: 500; - src: local('Montserrat Medium'), local('Montserrat-Medium'), url('../fonts/Montserrat-Medium.woff2') format('woff2'); -} - -@font-face { - font-family: 'Montserrat'; - font-style: normal; - font-weight: 600; - src: local('Montserrat SemiBold'), local('Montserrat-SemiBold'), url('../fonts/Montserrat-SemiBold.woff2') format('woff2'); -} - -@font-face { - font-family: 'Montserrat'; - font-style: normal; - font-weight: 700; - src: local('Montserrat Bold'), local('Montserrat-Bold'), url('../fonts/Montserrat-Bold.woff2') format('woff2'); -} - -@font-face { - font-family: 'Montserrat'; - font-style: normal; - font-weight: 800; - src: local('Montserrat ExtraBold'), local('Montserrat-ExtraBold'), url('../fonts/Montserrat-ExtraBold.woff2') format('woff2'); -} - -html { - height: 100%; } - -body { - padding: 0; - margin: 0; - -webkit-font-smoothing: antialiased; - -webkit-text-size-adjust: 100%; - -webkit-tap-highlight-color: transparent; } - -main { - display: block; } - -@-moz-document url-prefix() { - body { - font-weight: 200; - -moz-osx-font-smoothing: grayscale; } } - -.visually-hidden { - position: absolute; - overflow: hidden; - clip: rect(0 0 0 0); - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - border: 0; } - -.page-body { - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; - height: 100%; - min-width: 1200px; - padding: 0; - margin: 0; - font-size: 17px; - line-height: 1.2; - font-family: "Montserrat", "Arial", sans-serif; - color: #000000; - background-color: #f2f2f2; } - -.page-body__container { - -ms-flex-positive: 1; - flex-grow: 1; - position: relative; - z-index: 0; - -webkit-box-sizing: border-box; - box-sizing: border-box; - width: 1200px; - padding: 0 10px; - margin: 0 auto; } - .page-body__container::after { - content: ""; - position: absolute; - z-index: -1; - top: 0; - bottom: 0; - left: 130px; - width: 2px; - background-color: rgba(255, 255, 255, 0.58); } - -.page-body__page-main { - -ms-flex-positive: 1; - flex-grow: 1; - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; } - -.btn { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - -ms-flex-pack: center; - justify-content: center; - padding: 4px 16px 5px; - font-weight: 500; - font-size: 17px; - line-height: 21px; - font-family: inherit; - text-decoration: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - border: none; - border-radius: 15px; - cursor: pointer; - -webkit-transition: opacity 0.2s; - transition: opacity 0.2s; } - .btn:hover { - opacity: 0.8; } - .btn:active { - opacity: 0.6; } - .btn--big { - padding: 17px 29px 16px; - font-size: 18px; - line-height: 21px; - border-radius: 27px; } - .btn--yellow { - color: #424242; - background-color: #ffd054; } - .btn--blue { - color: #ffffff; - background-color: #0d8ae4; } - .btn:disabled { - opacity: 0.46; - cursor: default; } - -.page-header { - padding-top: 23px; - color: #ffffff; - background-color: #078ff0; - background-image: url("../img/header-bg.png"); - background-size: cover; - background-repeat: no-repeat; } - @media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi), (min-resolution: 1.5dppx) { - .page-header { - background-image: url("../img/header-bg@2x.png"); } } - -.page-header__container { - display: -ms-flexbox; - display: flex; - -ms-flex-align: start; - align-items: flex-start; - padding-left: 110px; } - -.page-header__logo { - display: block; - width: 42px; - height: 42px; - margin-right: 18px; } - -.trip-main { - -ms-flex-positive: 1; - flex-grow: 1; - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -ms-flex-align: start; - align-items: flex-start; - -ms-flex-pack: justify; - justify-content: space-between; - padding-bottom: 10px; } - .trip-main--hidden { - display: none; } - -.trip-main__trip-info { - width: 100%; - margin-bottom: 35px; } - -.trip-main__trip-controls { - margin-top: 36px; - width: 600px; } - -.trip-main__event-add-btn { - min-width: 170px; - margin-left: auto; } - .trip-main__event-add-btn::before { - content: "\002B\000A0"; } - .trip-main__event-add-btn--hidden { - display: none; } - -.trip-info { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - -ms-flex-pack: justify; } - -.trip-info__main { - width: 600px; } - -.trip-info__title { - margin: 0; - margin-bottom: 4px; - font-weight: 800; - font-size: 17px; - line-height: 21px; } - -.trip-info__dates { - margin: 0; - font-weight: 800; - font-size: 13px; - line-height: 16px; - text-transform: uppercase; - opacity: 0.8; } - -.trip-info__cost { - width: 170px; - margin: 0 0 0 auto; - font-weight: 700; - font-size: 21px; - line-height: 25px; - text-align: center; } - -.trip-controls__trip-tabs { - margin-bottom: 9px; } - -.trip-tabs { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -ms-flex-align: start; - align-items: flex-start; } - -.trip-tabs__btn { - display: block; - font-weight: 500; - font-size: 21px; - line-height: 25px; - text-decoration: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - color: inherit; - opacity: 0.8; - -webkit-transition: opacity 0.2s; - transition: opacity 0.2s; } - .trip-tabs__btn:not(:last-of-type) { - margin-right: 26px; } - .trip-tabs__btn:hover { - opacity: 1; } - .trip-tabs__btn:active { - opacity: 0.6; } - .trip-tabs__btn--active, .trip-tabs__btn--active:hover, .trip-tabs__btn--active:active { - opacity: 1; - cursor: default; } - -.trip-filters { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -ms-flex-align: start; - align-items: flex-start; } - .trip-filters--hidden { - display: none; } - -.trip-filters__filter:not(:last-of-type) { - margin-right: 19px; } - -.trip-filters__filter-label { - font-weight: 800; - font-size: 14px; - line-height: 17px; - letter-spacing: 0.5px; - text-transform: uppercase; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - opacity: 0.6; - cursor: pointer; - -webkit-transition: opacity 0.2s; - transition: opacity 0.2s; } - .trip-filters__filter-label:hover { - opacity: 1; } - .trip-filters__filter-label:active { - opacity: 0.6; } - .trip-filters__filter-input:checked + .trip-filters__filter-label { - opacity: 1; - cursor: default; } - .trip-filters__filter-input:disabled + .trip-filters__filter-label { - opacity: 0.4; - cursor: default; } - -.trip-events { - padding-top: 22px; } - .trip-events--hidden { - display: none; } - -.trip-events__trip-sort { - margin-bottom: 8px; } - -.trip-events__msg { - margin: 0; - margin-top: 280px; - font-weight: 500; - font-size: 44px; - line-height: 60px; - text-align: center; - opacity: 0.89; } - .trip-events__msg--hidden { - display: none; } - -.trip-events__list { - padding: 0; - margin: 0; - list-style: none; } - -.trip-events__item { - margin-bottom: 54px; - margin-left: auto; } - -.trip-sort { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -ms-flex-align: center; - align-items: center; } - -.trip-sort__item { - margin-right: 15px; - font-weight: 800; - font-size: 13px; - line-height: 16px; - letter-spacing: 0.6px; - text-transform: uppercase; - color: #b4b4b4; - cursor: default; - -webkit-transition: color 0.2s; - transition: color 0.2s; } - .trip-sort__item:last-child { - margin-right: 0; } - .trip-sort__item--day { - width: 55px; - text-align: center; - margin-right: 83px; } - .trip-sort__item--event { - width: 300px; } - .trip-sort__item--time { - width: 225px; } - .trip-sort__item--price { - width: 90px; } - -.trip-sort__btn { - display: -ms-inline-flexbox; - display: inline-flex; - -ms-flex-align: center; - align-items: center; - padding: 7px 15px 6px 22px; - border: 1px solid transparent; - border-radius: 14px; - cursor: pointer; } - .trip-sort__btn:hover { - color: #000000; } - .trip-sort__input:checked + .trip-sort__btn { - position: relative; - color: #000000; - border-color: #000000; } - .trip-sort__input:checked + .trip-sort__btn::before { - content: ""; - position: absolute; - top: 50%; - left: 8px; - width: 8px; - height: 8px; - background-color: #ffd054; - border-radius: 50%; - -webkit-transform: translateY(-50%); - transform: translateY(-50%); } - -.trip-sort__input:disabled + .trip-sort__btn { - opacity: 0.6; } -.trip-sort__input:disabled + .trip-sort__btn:hover { - color: #b4b4b4; - cursor: default; } - -.event { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - -webkit-box-sizing: border-box; - box-sizing: border-box; - padding: 20px; - font-size: 17px; - line-height: 21px; } - .event--edit { - display: block; - margin-left: 80px; - padding: 0; - background-color: #ffffff; - border-radius: 18px; - -webkit-box-shadow: 0 11px 20px rgba(0, 0, 0, 0.043); - box-shadow: 0 11px 20px rgba(0, 0, 0, 0.043); } - -.event__header { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - padding: 20px; } - -.event__date { - display: block; - min-width: 80px; - font-size: 13px; - line-height: 16px; - text-transform: uppercase; - text-align: left; } - -.event__type-wrapper { - position: relative; } - -.event__type { - -ms-flex-negative: 0; - flex-shrink: 0; - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - -ms-flex-pack: center; - justify-content: center; - -webkit-box-sizing: border-box; - box-sizing: border-box; - width: 42px; - height: 42px; - margin-right: 18px; - background-color: #ffffff; - border-radius: 50%; } - -.event__type-btn { - padding: 0; - border: 1px solid #0d8ae4; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: pointer; } - -.event__type-icon { - display: block; - width: 17px; - height: 17px; } - -.event__type-list { - position: absolute; - z-index: 2; - top: calc(100% + 7px); - left: -20px; - display: none; - width: 180px; - background-color: #ffffff; - border: 1px solid rgba(151, 151, 151, 0.169724); - -webkit-box-shadow: 0 11px 20px rgba(0, 0, 0, 0.219146); - box-shadow: 0 11px 20px rgba(0, 0, 0, 0.219146); - border-radius: 4px; } - -.event__type-toggle:checked + .event__type-list { - display: block; } - -.event__type-group { - padding: 13px 0 11px; - margin: 0; - border: none; } - .event__type-group:not(:last-of-type) { - border-bottom: 1px solid rgba(151, 151, 151, 0.33); } - -.event__type-item:not(:last-child) { - margin-bottom: 5px; } - -.event__type-label { - position: relative; - display: block; - padding: 5px 10px 5px 62px; - font-size: 17px; - line-height: 21px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: pointer; - -webkit-transition: color 0.2s; - transition: color 0.2s; } - .event__type-label::before { - content: ""; - position: absolute; - top: calc(50% - 3px); - left: 32px; - width: 17px; - height: 17px; - background-position: 0 0; - background-size: 17px 17px; - background-repeat: no-repeat; - -webkit-transform: translateY(-50%); - transform: translateY(-50%); } - .event__type-label--taxi::before { - background-image: url("../img/icons/taxi.png"); } - .event__type-label--bus::before { - background-image: url("../img/icons/bus.png"); } - .event__type-label--train::before { - background-image: url("../img/icons/train.png"); } - .event__type-label--ship::before { - background-image: url("../img/icons/ship.png"); } - .event__type-label--drive::before { - background-image: url("../img/icons/drive.png"); } - .event__type-label--flight::before { - background-image: url("../img/icons/flight.png"); } - .event__type-label--check-in::before { - background-image: url("../img/icons/check-in.png"); } - .event__type-label--sightseeing::before { - background-image: url("../img/icons/sightseeing.png"); } - .event__type-label--restaurant::before { - background-image: url("../img/icons/restaurant.png"); } - .event__type-label:hover { - color: #ffd054; } - -.event__type-input:checked + .event__type-label { - color: #000000; - background-color: #ffd054; } - .event__type-input:checked + .event__type-label::after { - content: ""; - position: absolute; - top: 50%; - left: 11px; - width: 8px; - height: 8px; - background-color: #ffffff; - border-radius: 50%; - -webkit-transform: translateY(-50%); - transform: translateY(-50%); } - -.event__title { - -ms-flex-negative: 0; - flex-shrink: 0; - width: 298px; - margin: 0; - margin-right: 15px; - font-weight: 400; - font-size: inherit; } - .event__title::first-letter { - text-transform: capitalize; } - -.event__schedule { - -ms-flex-negative: 0; - flex-shrink: 0; - width: 230px; - margin-right: 15px; } - -.event__time { - margin: 0; - margin-bottom: 4px; } - -.event__duration { - margin: 0; - font-size: 14px; - line-height: 17px; - letter-spacing: 0.6px; - text-transform: uppercase; - color: #999999; - opacity: 0.9; } - -.event__price { - -ms-flex-negative: 0; - flex-shrink: 0; - width: 90px; - margin: 0; - margin-right: 15px; } - -.event__selected-offers { - width: 220px; - padding: 0; - margin: 0; - font-weight: 600; - list-style: none; } - -.event__rollup-btn { - position: relative; - display: block; - width: 40px; - height: 40px; - padding: 0; - margin: 0; - background-color: transparent; - border: none; - cursor: pointer; } - .event__rollup-btn::after { - content: ""; - position: absolute; - top: calc(50% - 5px); - left: 50%; - width: 10px; - height: 10px; - border-right: 2px solid #000000; - border-bottom: 2px solid #000000; - -webkit-transform: translate(-50%, -50%) rotate(45deg); - transform: translate(-50%, -50%) rotate(45deg); - -webkit-transition: border-color 0.2s, -webkit-transform 0.6s; - transition: border-color 0.2s, -webkit-transform 0.6s; - transition: border-color 0.2s, transform 0.6s; - transition: border-color 0.2s, transform 0.6s, -webkit-transform 0.6s; } - .event__rollup-btn:hover::after { - border-color: #0d8ae4; } - -.event--edit .event__rollup-btn { - margin-left: auto; } -.event--edit .event__rollup-btn::after { - top: calc(50% + 3px); - -webkit-transform: translate(-50%, -50%) rotate(225deg); - transform: translate(-50%, -50%) rotate(225deg); } - -.event__field-group { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - padding: 5px 2px; - margin-right: 18px; - border-bottom: 1px solid #0d8ae4; } - .event__field-group--destination { - width: 290px; } - -.event__label { - margin-right: 5px; - cursor: pointer; } - .event__label::first-letter { - text-transform: capitalize; } - -.event__input { - -ms-flex-positive: 1; - flex-grow: 1; - padding: 0; - font-size: inherit; - line-height: inherit; - font-family: inherit; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - border: none; } - .event__input--destination { - width: 150px; } - .event__input--destination::-webkit-calendar-picker-indicator { - display: none; } - .event__input--time { - width: 130px; - text-align: center; } - .event__input--price { - width: 66px; } - -.event__save-btn { - margin-right: 18px; } - -.event__reset-btn { - display: block; - padding: 0; - font-weight: 500; - font-size: 17px; - line-height: 21px; - font-family: inherit; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - color: #0d8ae4; - background-color: transparent; - border: none; - cursor: pointer; - -webkit-transition: opacity 0.2s; - transition: opacity 0.2s; } - .event__reset-btn:hover { - opacity: 0.8; } - .event__reset-btn:active { - opacity: 0.6; } - -.event__favorite-btn { - margin: 0 10px; - margin-left: auto; - color: #ebebeb; - background-color: transparent; - border: none; - cursor: pointer; - outline: none; - -webkit-transition: color 0.2s; - transition: color 0.2s; } - .event__favorite-btn:hover, - .event__favorite-btn:focus { - color: rgba(255, 208, 84, 0.6); } - .event__favorite-btn--active { - color: #ffd054; - } - -.event__favorite-icon { - display: block; - width: 28px; - height: 28px; - fill: currentColor; - -webkit-transition: fill 0.2s; - transition: fill 0.2s; } - -.event__details { - padding: 20px 0 27px; - border-top: 2px solid #ffd054; } - -.event--blocked .event__details { - position: relative; } - .event--blocked .event__details::after { - content: ""; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: rgba(255, 255, 255, 0.8); - border-radius: 0 0 18px 18px; } - -.event__details-title { - padding-left: 20px; - margin: 0; - margin-bottom: 24px; - font-weight: 500; - font-size: 23px; - line-height: 27px; } - -.event__section:not(:last-child) { - margin-bottom: 28px; } - -.event__section--offers { - padding: 0 20px; } - -.event__section-title { - margin: 0; - font-weight: 800; - font-size: 14px; - line-height: 17px; - letter-spacing: 0.6px; - text-transform: uppercase; - color: #ffd054; } - .event__section-title--offers { - margin-bottom: 15px; } - .event__section-title--destination { - margin-bottom: 8px; - margin-left: 20px; } - -.event__available-offers { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - margin-bottom: -6px; } - -.event__offer::first-letter { - text-transform: capitalize; } - -.event__offer-selector { - margin-bottom: 6px; } - .event__offer-selector:not(:last-of-type) { - margin-right: 6px; } - -.event__offer-label { - display: block; - padding: 22px 30px 21px; - font-size: 17px; - line-height: 21px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: #f2f2f2; - border-radius: 32px; - cursor: pointer; - -webkit-transition: background-color 0.2s; - transition: background-color 0.2s; } - .event__offer-label:hover { - background-color: rgba(13, 138, 228, 0.6); } - .event__offer-label::first-letter { - text-transform: capitalize; } - -.event__offer-checkbox:checked + .event__offer-label { - background-color: #0d8ae4; } - -.event__destination-description { - width: 560px; - margin: 0; - margin-bottom: 16px; - margin-left: 20px; - font-size: 15px; - line-height: 18px; } - -.event__photos-container { - width: 100%; - overflow-x: scroll; } - -.event__photos-tape { - display: -ms-flexbox; - display: flex; - -ms-flex-align: start; - align-items: flex-start; } - -.event__photo { - display: block; - height: 152px; - width: auto; - border-radius: 4px; } - .event__photo:first-child { - padding-left: 20px; } - .event__photo:last-child { - padding-right: 20px; } - .event__photo:not(:last-child) { - margin-right: 5px; } diff --git a/build/fonts/Montserrat-Bold.woff2 b/build/fonts/Montserrat-Bold.woff2 deleted file mode 100644 index 3d0b409..0000000 Binary files a/build/fonts/Montserrat-Bold.woff2 and /dev/null differ diff --git a/build/fonts/Montserrat-ExtraBold.woff2 b/build/fonts/Montserrat-ExtraBold.woff2 deleted file mode 100644 index 0abb707..0000000 Binary files a/build/fonts/Montserrat-ExtraBold.woff2 and /dev/null differ diff --git a/build/fonts/Montserrat-Medium.woff2 b/build/fonts/Montserrat-Medium.woff2 deleted file mode 100644 index 9dc5c7f..0000000 Binary files a/build/fonts/Montserrat-Medium.woff2 and /dev/null differ diff --git a/build/fonts/Montserrat-Regular.woff2 b/build/fonts/Montserrat-Regular.woff2 deleted file mode 100644 index 70788c2..0000000 Binary files a/build/fonts/Montserrat-Regular.woff2 and /dev/null differ diff --git a/build/fonts/Montserrat-SemiBold.woff2 b/build/fonts/Montserrat-SemiBold.woff2 deleted file mode 100644 index 29cc1a9..0000000 Binary files a/build/fonts/Montserrat-SemiBold.woff2 and /dev/null differ diff --git a/build/img/header-bg.png b/build/img/header-bg.png deleted file mode 100644 index c7ccbda..0000000 Binary files a/build/img/header-bg.png and /dev/null differ diff --git a/build/img/header-bg@2x.png b/build/img/header-bg@2x.png deleted file mode 100644 index 26716e3..0000000 Binary files a/build/img/header-bg@2x.png and /dev/null differ diff --git a/build/img/icons/bus.png b/build/img/icons/bus.png deleted file mode 100644 index bb2bad6..0000000 Binary files a/build/img/icons/bus.png and /dev/null differ diff --git a/build/img/icons/check-in.png b/build/img/icons/check-in.png deleted file mode 100644 index 866702e..0000000 Binary files a/build/img/icons/check-in.png and /dev/null differ diff --git a/build/img/icons/drive.png b/build/img/icons/drive.png deleted file mode 100644 index ef3522d..0000000 Binary files a/build/img/icons/drive.png and /dev/null differ diff --git a/build/img/icons/flight.png b/build/img/icons/flight.png deleted file mode 100644 index 3e690ca..0000000 Binary files a/build/img/icons/flight.png and /dev/null differ diff --git a/build/img/icons/restaurant.png b/build/img/icons/restaurant.png deleted file mode 100644 index d677771..0000000 Binary files a/build/img/icons/restaurant.png and /dev/null differ diff --git a/build/img/icons/ship.png b/build/img/icons/ship.png deleted file mode 100644 index e854301..0000000 Binary files a/build/img/icons/ship.png and /dev/null differ diff --git a/build/img/icons/sightseeing.png b/build/img/icons/sightseeing.png deleted file mode 100644 index a87e06b..0000000 Binary files a/build/img/icons/sightseeing.png and /dev/null differ diff --git a/build/img/icons/taxi.png b/build/img/icons/taxi.png deleted file mode 100644 index f052e81..0000000 Binary files a/build/img/icons/taxi.png and /dev/null differ diff --git a/build/img/icons/train.png b/build/img/icons/train.png deleted file mode 100644 index 1dfd1f3..0000000 Binary files a/build/img/icons/train.png and /dev/null differ diff --git a/build/img/icons/transport.png b/build/img/icons/transport.png deleted file mode 100644 index 9200b63..0000000 Binary files a/build/img/icons/transport.png and /dev/null differ diff --git a/build/img/logo.png b/build/img/logo.png deleted file mode 100644 index f11c3ae..0000000 Binary files a/build/img/logo.png and /dev/null differ diff --git a/build/img/photos/1.jpg b/build/img/photos/1.jpg deleted file mode 100644 index 2234819..0000000 Binary files a/build/img/photos/1.jpg and /dev/null differ diff --git a/build/img/photos/2.jpg b/build/img/photos/2.jpg deleted file mode 100644 index 833055b..0000000 Binary files a/build/img/photos/2.jpg and /dev/null differ diff --git a/build/img/photos/3.jpg b/build/img/photos/3.jpg deleted file mode 100644 index b63e90a..0000000 Binary files a/build/img/photos/3.jpg and /dev/null differ diff --git a/build/img/photos/4.jpg b/build/img/photos/4.jpg deleted file mode 100644 index de034f4..0000000 Binary files a/build/img/photos/4.jpg and /dev/null differ diff --git a/build/img/photos/5.jpg b/build/img/photos/5.jpg deleted file mode 100644 index fa162ab..0000000 Binary files a/build/img/photos/5.jpg and /dev/null differ diff --git a/build/index.html b/build/index.html deleted file mode 100644 index 44c26b4..0000000 --- a/build/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - Big Trip - - - - - - -
-
-
-

Trip events

- - - - -
-
-
- - diff --git a/src/framework/api-service.js b/src/framework/api-service.js new file mode 100644 index 0000000..ef9f928 --- /dev/null +++ b/src/framework/api-service.js @@ -0,0 +1,70 @@ +/** + * Класс для отправки запросов к серверу + */ +export default class ApiService { + /** + * @param {string} endPoint Адрес сервера + * @param {string} authorization Авторизационный токен + */ + constructor(endPoint, authorization) { + this._endPoint = endPoint; + this._authorization = authorization; + } + + /** + * Метод для отправки запроса к серверу + * @param {Object} config Объект с настройками + * @param {string} config.url Адрес относительно сервера + * @param {string} [config.method] Метод запроса + * @param {string} [config.body] Тело запроса + * @param {Headers} [config.headers] Заголовки запроса + * @returns {Promise} + */ + async _load({ + url, + method = 'GET', + body = null, + headers = new Headers(), + }) { + headers.append('Authorization', this._authorization); + + const response = await fetch( + `${this._endPoint}/${url}`, + {method, body, headers}, + ); + + try { + ApiService.checkStatus(response); + return response; + } catch (err) { + ApiService.catchError(err); + } + } + + /** + * Метод для обработки ответа + * @param {Response} response Объект ответа + * @returns {Promise} + */ + static parseResponse(response) { + return response.json(); + } + + /** + * Метод для проверки ответа + * @param {Response} response Объект ответа + */ + static checkStatus(response) { + if (!response.ok) { + throw new Error(`${response.status}: ${response.statusText}`); + } + } + + /** + * Метод для обработки ошибок + * @param {Error} err Объект ошибки + */ + static catchError(err) { + throw err; + } +} diff --git a/src/framework/observable.js b/src/framework/observable.js new file mode 100644 index 0000000..026045d --- /dev/null +++ b/src/framework/observable.js @@ -0,0 +1,39 @@ +/** + * Класс, реализующий паттерн Наблюдатель. + */ +export default class Observable { + /** @type {Set} Множество функций типа observerCallback */ + #observers = new Set(); + + /** + * Метод, позволяющий подписаться на событие + * @param {observerCallback} observer Функция, которая будет вызвана при наступлении события + */ + addObserver(observer) { + this.#observers.add(observer); + } + + /** + * Метод, позволяющий отписаться от события + * @param {observerCallback} observer Функция, которую больше не нужно вызывать при наступлении события + */ + removeObserver(observer) { + this.#observers.delete(observer); + } + + /** + * Метод для оповещения подписчиков о наступлении события + * @param {*} event Тип события + * @param {*} payload Дополнительная информация + */ + _notify(event, payload) { + this.#observers.forEach((observer) => observer(event, payload)); + } +} + +/** + * Функция, которая будет вызвана при наступлении события + * @callback observerCallback + * @param {*} event Тип события + * @param {*} [payload] Дополнительная информация + */ diff --git a/src/framework/render.js b/src/framework/render.js new file mode 100644 index 0000000..2e089db --- /dev/null +++ b/src/framework/render.js @@ -0,0 +1,80 @@ +import AbstractView from './view/abstract-view.js'; + +/** @enum {string} Перечисление возможных позиций для отрисовки */ +const RenderPosition = { + BEFOREBEGIN: 'beforebegin', + AFTERBEGIN: 'afterbegin', + BEFOREEND: 'beforeend', + AFTEREND: 'afterend', +}; + +/** + * Функция для создания элемента на основе разметки + * @param {string} template Разметка в виде строки + * @returns {HTMLElement} Созданный элемент + */ +function createElement(template) { + const newElement = document.createElement('div'); + newElement.innerHTML = template; + + return newElement.firstElementChild; +} + +/** + * Функция для отрисовки элемента + * @param {AbstractView} component Компонент, который должен был отрисован + * @param {HTMLElement} container Элемент в котором будет отрисован компонент + * @param {string} place Позиция компонента относительно контейнера. По умолчанию - `beforeend` + */ +function render(component, container, place = RenderPosition.BEFOREEND) { + if (!(component instanceof AbstractView)) { + throw new Error('Can render only components'); + } + + if (container === null) { + throw new Error('Container element doesn\'t exist'); + } + + container.insertAdjacentElement(place, component.element); +} + +/** + * Функция для замены одного компонента на другой + * @param {AbstractView} newComponent Компонент, который нужно показать + * @param {AbstractView} oldComponent Компонент, который нужно скрыть + */ +function replace(newComponent, oldComponent) { + if (!(newComponent instanceof AbstractView && oldComponent instanceof AbstractView)) { + throw new Error('Can replace only components'); + } + + const newElement = newComponent.element; + const oldElement = oldComponent.element; + + const parent = oldElement.parentElement; + + if (parent === null) { + throw new Error('Parent element doesn\'t exist'); + } + + parent.replaceChild(newElement, oldElement); +} + +/** + * Функция для удаления компонента + * @param {AbstractView} component Компонент, который нужно удалить + */ +function remove(component) { + if (component === null) { + return; + } + + if (!(component instanceof AbstractView)) { + throw new Error('Can remove only components'); + } + + component.element.remove(); + component.removeElement(); +} + +export {RenderPosition, createElement, render, replace, remove}; diff --git a/src/framework/ui-blocker/ui-blocker.css b/src/framework/ui-blocker/ui-blocker.css new file mode 100644 index 0000000..489756d --- /dev/null +++ b/src/framework/ui-blocker/ui-blocker.css @@ -0,0 +1,49 @@ +.ui-blocker { + display: none; + place-content: center; + position: fixed; + top: 0; + left: 0; + min-width: 100%; + min-height: 100%; + z-index: 1000; + cursor: wait; + background-color: rgba(255, 255, 255, 0.5); +} + +.ui-blocker::before { + content: ""; + display: block; + border-radius: 50%; + border: 6px solid #4285F4; + box-sizing: border-box; + animation: sweep 1s linear alternate infinite, + rotate 0.8s linear infinite; + width: 65px; + height: 65px; +} + +.ui-blocker--on { + display: grid; +} + +@keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@keyframes sweep { + 0% { + clip-path: polygon(0% 0%, 0% 0%, 0% 0%, 50% 50%, 0% 0%, 0% 0%, 0% 0%); + } + 50% { + clip-path: polygon(0% 0%, 0% 100%, 0% 100%, 50% 50%, 100% 0%, 100% 0%, 0% 0%); + } + 100% { + clip-path: polygon(0% 0%, 0% 100%, 100% 100%, 50% 50%, 100% 100%, 100% 0%, 0% 0%); + } +} diff --git a/src/framework/ui-blocker/ui-blocker.js b/src/framework/ui-blocker/ui-blocker.js new file mode 100644 index 0000000..db9ab7b --- /dev/null +++ b/src/framework/ui-blocker/ui-blocker.js @@ -0,0 +1,74 @@ +import './ui-blocker.css'; + +/** + * Класс для блокировки интерфейса + */ +export default class UiBlocker { + /** @type {number} Время до блокировки интерфейса в миллисекундах */ + #lowerLimit; + + /** @type {number} Минимальное время блокировки интерфейса в миллисекундах */ + #upperLimit; + + /** @type {HTMLElement|null} Элемент, блокирующий интерфейс */ + #element; + + /** @type {number} Время вызова метода block */ + #startTime; + + /** @type {number} Время вызова метода unblock */ + #endTime; + + /** @type {number} Идентификатор таймера */ + #timerId; + + /** + * @param {Object} config Объект с настройками блокировщика + * @param {number} config.lowerLimit Время до блокировки интерфейса в миллисекундах. Если вызвать метод unblock раньше, то интерфейс заблокирован не будет + * @param {number} config.upperLimit Минимальное время блокировки в миллисекундах. Минимальная длительность блокировки + */ + constructor({lowerLimit, upperLimit}) { + this.#lowerLimit = lowerLimit; + this.#upperLimit = upperLimit; + + this.#element = document.createElement('div'); + this.#element.classList.add('ui-blocker'); + document.body.append(this.#element); + } + + /** Метод для блокировки интерфейса */ + block() { + this.#startTime = Date.now(); + this.#timerId = setTimeout(() => { + this.#addClass(); + }, this.#lowerLimit); + } + + /** Метод для разблокировки интерфейса */ + unblock() { + this.#endTime = Date.now(); + const duration = this.#endTime - this.#startTime; + + if (duration < this.#lowerLimit) { + clearTimeout(this.#timerId); + return; + } + + if (duration >= this.#upperLimit) { + this.#removeClass(); + return; + } + + setTimeout(this.#removeClass, this.#upperLimit - duration); + } + + /** Метод, добавляющий CSS-класс элементу */ + #addClass = () => { + this.#element.classList.add('ui-blocker--on'); + }; + + /** Метод, убирающий CSS-класс с элемента */ + #removeClass = () => { + this.#element.classList.remove('ui-blocker--on'); + }; +} diff --git a/src/framework/view/abstract-stateful-view.js b/src/framework/view/abstract-stateful-view.js new file mode 100644 index 0000000..66f68ae --- /dev/null +++ b/src/framework/view/abstract-stateful-view.js @@ -0,0 +1,52 @@ +import AbstractView from './abstract-view.js'; + +/** + * Абстрактный класс представления с состоянием + */ +export default class AbstractStatefulView extends AbstractView { + /** @type {Object} Объект состояния */ + _state = {}; + + /** + * Метод для обновления состояния и перерисовки элемента + * @param {Object} update Объект с обновлённой частью состояния + */ + updateElement(update) { + if (!update) { + return; + } + + this._setState(update); + + this.#rerenderElement(); + } + + /** + * Метод для восстановления обработчиков после перерисовки элемента + * @abstract + */ + _restoreHandlers() { + throw new Error('Abstract method not implemented: restoreHandlers'); + } + + /** + * Метод для обновления состояния + * @param {Object} update Объект с обновлённой частью состояния + */ + _setState(update) { + this._state = structuredClone({...this._state, ...update}); + } + + /** Метод для перерисовки элемента */ + #rerenderElement() { + const prevElement = this.element; + const parent = prevElement.parentElement; + this.removeElement(); + + const newElement = this.element; + + parent.replaceChild(newElement, prevElement); + + this._restoreHandlers(); + } +} diff --git a/src/framework/view/abstract-view.css b/src/framework/view/abstract-view.css new file mode 100644 index 0000000..04070e2 --- /dev/null +++ b/src/framework/view/abstract-view.css @@ -0,0 +1,27 @@ +.shake { + animation: shake 0.6s; + position: relative; + z-index: 10; +} + +@keyframes shake { + 0%, + 100% { + transform: translateX(0); + } + + 10%, + 30%, + 50%, + 70%, + 90% { + transform: translateX(-5px); + } + + 20%, + 40%, + 60%, + 80% { + transform: translateX(5px); + } +} diff --git a/src/framework/view/abstract-view.js b/src/framework/view/abstract-view.js new file mode 100644 index 0000000..fa2a552 --- /dev/null +++ b/src/framework/view/abstract-view.js @@ -0,0 +1,65 @@ +import {createElement} from '../render.js'; +import './abstract-view.css'; + +/** @const {string} Класс, реализующий эффект "покачивания головой" */ +const SHAKE_CLASS_NAME = 'shake'; + +/** @const {number} Время анимации в миллисекундах */ +const SHAKE_ANIMATION_TIMEOUT = 600; + +/** + * Абстрактный класс представления + */ +export default class AbstractView { + /** @type {HTMLElement|null} Элемент представления */ + #element = null; + + constructor() { + if (new.target === AbstractView) { + throw new Error('Can\'t instantiate AbstractView, only concrete one.'); + } + } + + /** + * Геттер для получения элемента + * @returns {HTMLElement} Элемент представления + */ + get element() { + if (!this.#element) { + this.#element = createElement(this.template); + } + + return this.#element; + } + + /** + * Геттер для получения разметки элемента + * @abstract + * @returns {string} Разметка элемента в виде строки + */ + get template() { + throw new Error('Abstract method not implemented: get template'); + } + + /** Метод для удаления элемента */ + removeElement() { + this.#element = null; + } + + /** + * Метод, реализующий эффект "покачивания головой" + * @param {shakeCallback} [callback] Функция, которая будет вызвана после завершения анимации + */ + shake(callback) { + this.element.classList.add(SHAKE_CLASS_NAME); + setTimeout(() => { + this.element.classList.remove(SHAKE_CLASS_NAME); + callback?.(); + }, SHAKE_ANIMATION_TIMEOUT); + } +} + +/** + * Функция, которая будет вызвана методом shake после завершения анимации + * @callback shakeCallback + */