├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .stylelintrc
├── GUIDE.md
├── GULP-GUIDE.md
├── README.md
├── csscomb.json
├── eslintconfig.cjs
├── gulp
├── compileHtml.mjs
├── compileScripts.mjs
├── compileStyles.mjs
├── copyAssets.mjs
└── optimizeImages.mjs
├── gulpfile.js
├── package.json
├── source
├── fonts
│ ├── rouble.woff
│ └── rouble.woff2
├── html
│ ├── base
│ │ ├── footer.html
│ │ ├── head.html
│ │ ├── header.html
│ │ └── modal.html
│ ├── components.html
│ ├── components
│ │ ├── modal-feedback.html
│ │ └── modal-success.html
│ ├── index.html
│ └── sitemap.html
├── img
│ ├── bg
│ │ └── placeholder.jpg
│ ├── content
│ │ └── placeholder.jpg
│ ├── slides
│ │ └── placeholder.jpg
│ ├── sprite
│ │ └── icon-close.svg
│ └── svg
│ │ └── not-needed-in-sprite.svg
├── js
│ ├── main.js
│ ├── modules
│ │ └── modals
│ │ │ ├── init-modals.js
│ │ │ └── modals.js
│ ├── utils
│ │ ├── focus-lock.js
│ │ ├── ios-checker.js
│ │ ├── ios-vh-fix.js
│ │ └── scroll-lock.js
│ ├── vendor.js
│ └── vendor
│ │ ├── focus-visible-polyfill.js
│ │ └── swiper.js
└── sass
│ ├── blocks
│ ├── btn.scss
│ ├── footer.scss
│ ├── header.scss
│ ├── main-nav.scss
│ └── modal.scss
│ ├── functions.scss
│ ├── global
│ ├── container.scss
│ ├── fonts.scss
│ ├── reboot.scss
│ └── utils.scss
│ ├── keyframes.scss
│ ├── mixins.scss
│ ├── style.scss
│ ├── variables.scss
│ └── vendor
│ ├── normalize.scss
│ └── swiper.scss
├── stylelintconfig.cjs
└── webpack.config.cjs
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Файл с настройками для редактора.
2 | #
3 | # Если вы разрабатываете в редакторе WebStorm, BBEdit, Coda или SourceLair
4 | # этот файл уже поддерживается и не нужно производить никаких дополнительных
5 | # действий.
6 | #
7 | # Если вы ведёте разработку в другом редакторе, зайдите
8 | # на https://editorconfig.org и в разделе «Download a Plugin»
9 | # скачайте дополнение для вашего редактора.
10 |
11 | root = true
12 |
13 | [*]
14 | charset = utf-8
15 | end_of_line = lf
16 | indent_size = 2
17 | indent_style = space
18 | insert_final_newline = true
19 | trim_trailing_whitespace = true
20 |
21 | [*.md]
22 | trim_trailing_whitespace = false
23 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | source/js/vendor/*.js
3 | gulpfile.js
4 | webpack.config.js
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | root: true
2 |
3 | parserOptions:
4 | ecmaVersion: 2015
5 | sourceType: 'module'
6 |
7 | env:
8 | es6: true
9 | browser: true
10 | commonjs: true
11 |
12 | extends: "./eslintconfig.cjs"
13 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 |
3 | *.png binary
4 | *.jpg binary
5 | *.jpeg binary
6 | *.avif binary
7 | *.webp binary
8 | *.webm binary
9 | *.gif binary
10 | *.woff binary
11 | *.woff2 binary
12 | *.pdf binary
13 | *.mp3 binary
14 | *.mp4 binary
15 | *.avi binary
16 | *.mov binary
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 | .vscode
4 | package-lock.json
5 | *.ai
6 | *.log
7 | *.psd
8 | *.sublime*
9 | node_modules/
10 | npm-debug.*
11 | source/css/*.css
12 | source/css/*.map
13 | Thumbs.db
14 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./stylelintconfig.cjs"
3 | }
4 |
--------------------------------------------------------------------------------
/GUIDE.md:
--------------------------------------------------------------------------------
1 | # Гайд по работе со сборкой
2 | ## Основные отличия от старой сборки
3 | 1. PNG \ JPG автоматически не ужимаются, webp не создается.
4 | При необходимости используйте `npm run webp` и `npm run imagemin`
5 | Webp - создание в source, imagemin - оптимизация в build
6 | 2. `PostHtml` плагин для инклюдов заменен на `gulp-file-include`
7 | 3. Добавлены дополнительные вотчеры, чтобы при изменении js и картинок не приходилось перезапускать сборку
8 | 4. Добавлена **[сущность модалок](#Модалки)**
9 | 5. Немного изменен eslint. Косые кавычки теперь должны использоваться только в шаблонных строках
10 | 6. Добавлены различные фиксы для ie11
11 | 7. Обновлена файловая структура, добавлен пример `container.scss`, в `reboot.scss` собраны различные ресеты \ кроссбраузерные фиксы
12 |
13 | ❗ Обязательно обратите внимание на `chrome autofill background removal` в `reboot.scss`. Там необходимо заменить цвета на используемые в проекте.
14 |
15 | ## Gulp-file-include
16 | Так как в текущее время разработка большинства проектов связана с созданием копонентов - возникла необходимость передавать переменные в инклюды. Ведь в стандартном `posthtml-include` чтобы расширить компонент модификатором - приходилось создавать новый html файл, копипастить содержимое. И при изменении базы компонента - приходилось бы редактировать и модифицированную версию, а это бы нарушало принцип компонентного подхода.
17 |
18 | На помощь приходит плагин `gulp-file-include`.
19 |
20 | ❗ Важное уточнение. Если у вас крупный проект, множестно расширяемых компонентов - используйте `pug` или другой шаблонизатор, заточенный под такие задачи. Этот плагин не решит тех задач, которые стоят перед шаблонизаторами.
21 |
22 | ### Стандартный include
23 | Раньше для инклюда с `post-html` мы использовали такой синтаксис
24 |
25 | ```html
26 |
27 | ```
28 |
29 | Теперь он станет таким
30 |
31 | ```html
32 | @@include("source/html/components/card.html")
33 | ```
34 |
35 | ### Использования переменных.
36 | Важно понимать, что `gulp-file-include` подчиняется законам javascript, но у него есть свои особенности и ограничения.
37 |
38 | #### Объявление переменной
39 | 1. Локальное
40 |
41 | ```
42 | @@include("source/html/components/card.html", {
43 | "title": "text",
44 | "mod": "tall"
45 | })
46 | ```
47 |
48 | ❗ Если внутри одного инклюда используется другой инклюд - переменная переданная в родителя не может быть прочитана у потомков. Чтобы решить эту проблему требуется создать цепочку передачи переменных.
49 |
50 | ```
51 | @@include("source/html/components/card-child.html", {
52 | "title": "@@text"
53 | })
54 | ```
55 |
56 | 2. Глобальное
57 |
58 | Глобальное объявление переменных доступно только в `gulpfile.js`, объявить глобальную переменную на страницу невозможно.
59 |
60 | Глобальную переменную можно переопределить для конкретного инклюда просто передав её в него с другим значением.
61 |
62 | ❗ Тщательно подбирайте нейминг для глобальных переменных. Не `navLinks`, а `headerNavLinks`, чтобы любой разработчик мог уже в gulp понять область применения этой глобальной переменной. Также нельзя передавать в глобал переменную `mod`.
63 |
64 | ```js
65 | gulp.task(`html`, function () {
66 | return gulp.src([`source/html/*.html`])
67 | .pipe(fileinclude({
68 | prefix: `@@`,
69 | basepath: `@root`,
70 | context: {
71 | featuresCardTitle: "text",
72 | featuresCardMod: "tall"
73 | }
74 | }))
75 | .pipe(gulp.dest(`build`));
76 | });
77 | ```
78 |
79 | #### Обращение к переменной
80 |
81 | ```html
82 |
83 |
@@title
84 |
85 | ```
86 |
87 | ### Условия
88 | Переменные и условия можно вставить буквально куда угодно. В классы, атрибуты и пр.
89 |
90 | Чаще всего условия будут применяться, чтобы добавить модификатор к компоненту или если на одной странице в компонент переменная передается, а на другой нет.
91 |
92 | ```html
93 |
94 | @@if (context.title) {
95 |
@@title
96 | }
97 |
98 | ```
99 |
100 | Также условия понадобятся для того чтобы `header` на разных страницах имел разные активные ссылки, а на главной логотип был без `href`(старый плагин так не умел, а некоторые заказчики просили отобразить смену пунктов меню и для этого приходилось плодить компонент шапки).
101 |
102 | В текущей сборке в `header` уже добавлены условия для решения этой задачи. Вам остается лишь копипастить пункты меню до нужного количества увеичивая индексы `context.headerActiveLinkIndex === 2` → `context.headerActiveLinkIndex === 3`.
103 |
104 | #### Почему в условиях стоит применять `(context.mod)`, а не `(mod)` как указано в спецификации плагина?
105 | Проблема может возникнуть, если компонент на одной странице передается с переменной, а на другой без + если данная переменная отсутствует в глобале в `gulpfile.js`.
106 | Если в таком случае написать `@@if (mod) { header--@@mod }`, то сборка выдаст ошибку 'mod is not defined', ведь на одной из страниц мы его не передаем.
107 |
108 | Решение этой проблемы - использование объекта `context`, который в этом плагине отвечает за попадание переменных в глобал `@@if (context.mod) { header--@@mod }`.
109 |
110 | ❗ Нельзя в переменных использовать дефис `test-1`, если в компоненте вы их вызываете через `context.test-1`
111 |
112 | ### Циклы.
113 | Циклы в этом плагине очень недружелюбны, по ним нет адекватной информации в спецификации.
114 |
115 | **В 99% случаев если на проекте необходимы циклы и такого рода компоненты - лучше использовать `pug`.**
116 |
117 | Но все же полезно будет узнать, что еще умеет этот плагин.
118 |
119 | Плагин парсит @@if раньше чем @@for. Соответственно если поместить @@if внутрь @@for, к примеру, `@@if (headerActiveLinkIndex == `+i+`)` - консоль выдаст ошибку, ведь на момент парсинга условия переменная `i` от цикла еще не была создана. Выход из этой проблемы - использование тернарного оператора.
120 |
121 | ```html
122 |
123 |
124 | @@for (var i = 0; i < headerLinks.length; i++) {
125 | `+(i == headerActiveLinkIndex ?
126 | ''+headerNavLinks[i].text+' ' :
127 | ''+headerNavLinks[i].text+' ')+`
128 | }
129 |
130 |
131 | ```
132 |
133 | Также в gulp необходимо задать глобальные переменные, чтобы не копипастить их на каждую страницу, что нарушало бы логику компонентности
134 |
135 | ```
136 | context: {
137 | headerActiveLinkIndex: null,
138 | headerNavLinks: [
139 | {
140 | "text": "О компании",
141 | "href": "about.html"
142 | },
143 | {
144 | "text": "Каталог",
145 | "href": "catalog.html"
146 | },
147 | {
148 | "text": "Контакты",
149 | "href": "contacts.html"
150 | }
151 | ]
152 | ```
153 |
154 | headerActiveLinkIndex переопределяется для каждого компонента по необходимости. Если же мы не задаем эту переменную для определенной страницы - сборка не выдаст ошибку. Ведь в глобале есть `null`.
155 |
156 | ---
157 |
158 | ## Модалки
159 | В сборку добавлена сущность модалок.
160 |
161 | `html/base/modal.html` + `sass/blocks/modal.scss` + `js/utils/modal.js` + `js/modules/init-modals.js`
162 |
163 | Также в main.html добавлены две модалки для примера.
164 |
165 | ### Создаем модалку
166 | 1. В html для интерактивных элементов вызывающих модалку вам необходимо добавить `data-modal="x"`.
167 | 2. В инклюд передаем `name` соответствующий второй части имени файла модалки в `html/components/modal-name` и соответствующий тому что вы передали в 1 пункте, который также автоматически создаст модификатор у модалки `modal--@@name`
168 | 3. Передаем `fitContent`, если у вас стоит задача, чтобы модалка подстраивалась под ширину контента
169 | 4. Передаем `mod`, если нам необходим дополнительный модификатор (к примеру, `no-overlay`)
170 |
171 | ```html
172 |
173 |
174 |
175 |
176 | @@include("source/html/base/modal.html", {
177 | "name": "success",
178 | "fitContent": true,
179 | "mod": "some-mod"
180 | })
181 | ```
182 |
183 | В `js/modules/init-modals.js` необходимо найти модалку + ссылки на нее и передать как аргументы в `setupModal()`.
184 |
185 | ```js
186 | // аргументы setupModal(modal, closeCallback, modalBtns, openCallback, noPrevDefault)
187 | // возможна инициализация только с первыми аргументом,
188 | // если вам нужно открывать модалку в другом месте под какими-нибудь условиями
189 | const initModals = () => {
190 | if (modalFeedback && modalFeedbackBtns.length) {
191 | setupModal(modalFeedback, false, modalFeedbackBtns, false, false);
192 | }
193 | if (modalSuccess && modalSuccessBtns.length) {
194 | setupModal(modalSuccess, false, modalSuccessBtns);
195 | }
196 | };
197 | ```
198 |
199 | Готово. Остается поправить стили через модификатор при необходимости.
200 | Также стоит учесть, если на проекте в разных модалках одинаковый размер заголовков, вертикальные отступы и т.д. - такие элементы имеет смысл внести в базу `sass/blocks/modal.scss`, а не стилизовать каждый раз отдельно для каждой модалки.
201 |
202 | ❗ Новые базовые модификаторы пишем в `sass/blocks/modal.scss`, а под каждую новую модалку, если ей требуется дополнительная стилизация стоит создавать новый scss файл, а не плодить модификаторы в базовом `modal.scss`.
203 |
204 | ---
205 |
206 | ### Если вы незнакомы с gulp или вам не до конца понятна как работает сборка - предлагаем ознакомиться с [кратким описанием работы gulp](/GULP-GUIDE.md) 📘
207 |
--------------------------------------------------------------------------------
/GULP-GUIDE.md:
--------------------------------------------------------------------------------
1 | # Краткоe описание работы gulp
2 |
3 | Запустив команду `npm i` на проект установятся все зависимости, необходимые для работы.
4 |
5 | Основные таски сборки представлены в виде модулей и сложены в папку gulp находящуюся в корне проекта
6 |
7 | В gulpfile.js мы импортируем зависимости.
8 |
9 | ```js
10 | import gulp from 'gulp'; // основа gulp
11 | import browserSync from 'browser-sync'; // дополнительный плагин
12 | import del from 'del'; // дополнительный плагин
13 | ```
14 |
15 | Далее мы импортируем таски из модулей.
16 |
17 | ```js
18 | import styles from './gulp/compileStyles.mjs'; // стили
19 | import { copy, copyImages, copySvg } from './gulp/copyAssets.mjs'; // копирование
20 | import js from './gulp/compileScripts.mjs'; // js
21 | import { svgo, sprite, createWebp, optimizeImages } from './gulp/optimizeImages.mjs'; // работа с графикой
22 | import html from './gulp/compileHtml.mjs'; // html
23 | ```
24 |
25 | Пример модуля с таской
26 |
27 | Сначала мы импортируем все необходимые зависимости.
28 |
29 | ```js
30 | import gulp from 'gulp';
31 | import plumber from 'gulp-plumber';
32 | import dartSass from 'sass';
33 | import gulpSass from 'gulp-sass';
34 | import postcss from 'gulp-postcss';
35 | import autoprefixer from 'autoprefixer';
36 | import csso from 'gulp-csso';
37 | import gcmq from 'gulp-group-css-media-queries';
38 | import rename from 'gulp-rename';
39 | ```
40 |
41 | Далее создаём функцию.
42 |
43 | ```js
44 | const sass = gulpSass(dartSass);
45 |
46 | const compileStyles = () =>
47 | gulp.src('source/sass/style.scss', {sourcemaps: true})
48 | .pipe(plumber())
49 | .pipe(sass())
50 | .pipe(postcss([autoprefixer({
51 | grid: true,
52 | })]))
53 | .pipe(gcmq()) // выключите, если в проект импортятся шрифты через ссылку на внешний источник
54 | .pipe(gulp.dest('build/css'))
55 | .pipe(csso())
56 | .pipe(rename('style.min.css'))
57 | .pipe(gulp.dest('build/css', {sourcemaps: '.'}));
58 | ```
59 | Далее экспортим функцию.
60 |
61 | ```js
62 | export default compileStyles;
63 | ```
64 |
65 | В gulpfile.js оставдены только базовые таски.
66 |
67 | ```js
68 | const server = browserSync.create();
69 | const streamStyles = () => styles().pipe(server.stream());
70 | const clean = () => del('build');
71 | const refresh = (done) => {
72 | server.reload();
73 | done();
74 | };
75 |
76 |
77 | const syncServer = () => {
78 | server.init({
79 | server: 'build/',
80 | index: 'sitemap.html',
81 | notify: false,
82 | open: true,
83 | cors: true,
84 | ui: false,
85 | });
86 |
87 | gulp.watch('source/pug/**/*.pug', gulp.series(html, refresh));
88 | gulp.watch('source/sass/**/*.{scss,sass}', streamStyles);
89 | gulp.watch('source/js/**/*.{js,json}', gulp.series(js, refresh));
90 | gulp.watch('source/data/**/*.{js,json}', gulp.series(copy, refresh));
91 | gulp.watch('source/img/**/*.svg', gulp.series(copySvg, sprite, html, refresh));
92 | gulp.watch('source/img/**/*.{png,jpg,webp}', gulp.series(copyImages, html, refresh));
93 |
94 | gulp.watch('source/favicon/**', gulp.series(copy, refresh));
95 | gulp.watch('source/video/**', gulp.series(copy, refresh));
96 | gulp.watch('source/downloads/**', gulp.series(copy, refresh));
97 | gulp.watch('source/*.php', gulp.series(copy, refresh));
98 | };
99 | ```
100 |
101 | Создание и экспорт комманд.
102 |
103 | ```js
104 | const build = gulp.series(clean, svgo, copy, styles, sprite, js, html);
105 | const start = gulp.series(build, syncServer);
106 |
107 | export { optimizeImages as imagemin, createWebp as webp, build, start };
108 | ```
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## [Гайд по работе со сборкой](/GUIDE.md) 📕
2 |
3 | ## Краткая инструкция по работе
4 | Для начала работы у вас должент быть установлен **Node.js** 14 версии
5 |
6 | ### Основные команды для работы
7 | - Установка - `npm i`
8 | - Запуск локального сервера - `npm start`
9 | - Сборка проекта, минификация скриптов
10 | и оптимизация изображений перед деплоем на прод - `npm run build`
11 | - Запуск тестирования на соответствия кодгайдам - `npm test`
12 | - Создание webp изображений в директории source - `npm run webp`
13 |
14 | ### Вся разработка ведётся в директории `source`
15 | ### Итоговый код попадает в директорию `build`
16 |
--------------------------------------------------------------------------------
/csscomb.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": [
3 | ".git/**",
4 | "node_modules/**"
5 | ],
6 |
7 | "always-semicolon": true,
8 | "block-indent": " ",
9 | "color-case": "lower",
10 | "color-shorthand": false,
11 | "element-case": "lower",
12 | "eof-newline": true,
13 | "leading-zero": true,
14 | "quotes": "double",
15 | "remove-empty-rulesets": true,
16 | "sort-order-fallback": "abc",
17 | "space-before-colon": "",
18 | "space-after-colon": " ",
19 | "space-before-combinator": " ",
20 | "space-after-combinator": " ",
21 | "space-between-declarations": "\n",
22 | "space-before-opening-brace": " ",
23 | "space-after-opening-brace": "\n",
24 | "space-before-selector-delimiter": "",
25 | "space-after-selector-delimiter": "\n",
26 | "space-before-closing-brace": "\n",
27 | "strip-spaces": true,
28 | "tab-size": 2,
29 | "unitless-zero": true,
30 | "vendor-prefix-align": true,
31 | "sort-order": [
32 | [
33 | "content",
34 | "position",
35 | "top",
36 | "right",
37 | "bottom",
38 | "left",
39 | "z-index"
40 | ],
41 | [
42 | "display",
43 | "-webkit-box",
44 | "-webkit-flex",
45 | "-moz-box",
46 | "-ms-flexbox",
47 | "flex",
48 | "-webkit-box-flex",
49 | "-webkit-flex-grow",
50 | "-moz-box-flex",
51 | "-ms-flex-positive",
52 | "flex-grow",
53 | "-webkit-flex-shrink",
54 | "-ms-flex-negative",
55 | "flex-shrink",
56 | "-webkit-flex-basis",
57 | "-ms-flex-preferred-size",
58 | "flex-basis",
59 | "-webkit-flex-flow",
60 | "-ms-flex-flow",
61 | "flex-flow",
62 | "-webkit-box-orient",
63 | "-webkit-box-direction",
64 | "-webkit-flex-direction",
65 | "-moz-box-orient",
66 | "-moz-box-direction",
67 | "-ms-flex-direction",
68 | "flex-direction",
69 | "-webkit-flex-wrap",
70 | "-ms-flex-wrap",
71 | "flex-wrap",
72 | "-webkit-box-pack",
73 | "-webkit-justify-content",
74 | "-moz-box-pack",
75 | "-ms-flex-pack",
76 | "justify-content",
77 | "-webkit-align-content",
78 | "-ms-flex-line-pack",
79 | "align-content",
80 | "-webkit-box-align",
81 | "-webkit-align-items",
82 | "-moz-box-align",
83 | "-ms-flex-align",
84 | "align-items",
85 | "-webkit-box-ordinal-group",
86 | "-webkit-order",
87 | "-moz-box-ordinal-group",
88 | "-ms-flex-order",
89 | "order",
90 | "-webkit-align-self",
91 | "-ms-flex-item-align",
92 | "-ms-grid-row-align",
93 | "align-self",
94 | "float",
95 | "clear",
96 | "-webkit-box-sizing",
97 | "-moz-box-sizing",
98 | "box-sizing",
99 | "width",
100 | "min-width",
101 | "max-width",
102 | "height",
103 | "min-height",
104 | "max-height",
105 | "margin",
106 | "margin-top",
107 | "margin-right",
108 | "margin-bottom",
109 | "margin-left",
110 | "padding",
111 | "padding-top",
112 | "padding-right",
113 | "padding-bottom",
114 | "padding-left",
115 | "overflow",
116 | "-ms-overflow-x",
117 | "overflow-x",
118 | "-ms-overflow-y",
119 | "overflow-y",
120 | "-webkit-overflow-scrolling"
121 | ],
122 | [
123 | "list-style",
124 | "list-style-position",
125 | "list-style-type",
126 | "list-style-image",
127 | "border-collapse",
128 | "border-spacing",
129 | "table-layout",
130 | "empty-cells",
131 | "caption-side",
132 | "font",
133 | "font-style",
134 | "font-variant",
135 | "font-weight",
136 | "font-size",
137 | "line-height",
138 | "font-family",
139 | "vertical-align",
140 | "text-align",
141 | "direction",
142 | "color",
143 | "text-transform",
144 | "-webkit-text-decoration",
145 | "-moz-text-decoration",
146 | "text-decoration",
147 | "font-size-adjust",
148 | "font-stretch",
149 | "font-effect",
150 | "font-emphasize",
151 | "font-emphasize-position",
152 | "font-emphasize-style",
153 | "font-smooth",
154 | "-webkit-text-align-last",
155 | "-moz-text-align-last",
156 | "-ms-text-align-last",
157 | "text-align-last",
158 | "letter-spacing",
159 | "word-spacing",
160 | "white-space",
161 | "-webkit-text-emphasis",
162 | "text-emphasis",
163 | "-webkit-text-emphasis-color",
164 | "text-emphasis-color",
165 | "-webkit-text-emphasis-style",
166 | "text-emphasis-style",
167 | "-webkit-text-emphasis-position",
168 | "text-emphasis-position",
169 | "text-indent",
170 | "-ms-text-justify",
171 | "text-justify",
172 | "-ms-writing-mode",
173 | "text-outline",
174 | "text-wrap",
175 | "-ms-text-overflow",
176 | "-o-text-overflow",
177 | "text-overflow",
178 | "text-overflow-ellipsis",
179 | "text-overflow-mode",
180 | "text-orientation",
181 | "-ms-word-wrap",
182 | "word-wrap",
183 | "-ms-word-break",
184 | "word-break",
185 | "-moz-tab-size",
186 | "-o-tab-size",
187 | "tab-size",
188 | "-webkit-hyphens",
189 | "-moz-hyphens",
190 | "-ms-hyphens",
191 | "hyphens",
192 | "unicode-bidi",
193 | "-webkit-columns",
194 | "-moz-columns",
195 | "columns",
196 | "-webkit-column-count",
197 | "-moz-column-count",
198 | "column-count",
199 | "-webkit-column-fill",
200 | "-moz-column-fill",
201 | "column-fill",
202 | "-webkit-column-gap",
203 | "-moz-column-gap",
204 | "column-gap",
205 | "-webkit-column-rule",
206 | "-moz-column-rule",
207 | "column-rule",
208 | "-webkit-column-rule-color",
209 | "-moz-column-rule-color",
210 | "column-rule-color",
211 | "-webkit-column-rule-style",
212 | "-moz-column-rule-style",
213 | "column-rule-style",
214 | "-webkit-column-rule-width",
215 | "-moz-column-rule-width",
216 | "column-rule-width",
217 | "-webkit-column-span",
218 | "-moz-column-span",
219 | "column-span",
220 | "-webkit-column-width",
221 | "-moz-column-width",
222 | "column-width",
223 | "text-shadow",
224 | "page-break-after",
225 | "page-break-before",
226 | "page-break-inside"
227 | ],
228 | [
229 | "background",
230 | "background-color",
231 | "background-image",
232 | "-webkit-gradient",
233 | "-webkit-linear-gradient",
234 | "-moz-linear-gradient",
235 | "-o-linear-gradient",
236 | "linear-gradient",
237 | "background-repeat",
238 | "background-position",
239 | "-ms-background-position-x",
240 | "background-position-x",
241 | "-ms-background-position-y",
242 | "background-position-y",
243 | "-webkit-background-size",
244 | "-moz-background-size",
245 | "-o-background-size",
246 | "background-size",
247 | "-webkit-background-clip",
248 | "-moz-background-clip",
249 | "background-clip",
250 | "-webkit-background-origin",
251 | "-moz-background-origin",
252 | "-o-background-origin",
253 | "background-origin",
254 | "background-attachment",
255 | "-webkit-box-decoration-break",
256 | "box-decoration-break",
257 | "background-blend-mode",
258 | "border",
259 | "border-width",
260 | "border-style",
261 | "border-color",
262 | "border-top",
263 | "border-top-width",
264 | "border-top-style",
265 | "border-top-color",
266 | "border-right",
267 | "border-right-width",
268 | "border-right-style",
269 | "border-right-color",
270 | "border-bottom",
271 | "border-bottom-width",
272 | "border-bottom-style",
273 | "border-bottom-color",
274 | "border-left",
275 | "border-left-width",
276 | "border-left-style",
277 | "border-left-color",
278 | "-webkit-border-radius",
279 | "-moz-border-radius",
280 | "border-radius",
281 | "-webkit-border-top-left-radius",
282 | "-moz-border-radius-topleft",
283 | "border-top-left-radius",
284 | "-webkit-border-top-right-radius",
285 | "-moz-border-radius-topright",
286 | "border-top-right-radius",
287 | "-webkit-border-bottom-right-radius",
288 | "-moz-border-radius-bottomright",
289 | "border-bottom-right-radius",
290 | "-webkit-border-bottom-left-radius",
291 | "-moz-border-radius-bottomleft",
292 | "border-bottom-left-radius",
293 | "-webkit-border-image",
294 | "-moz-border-image",
295 | "-o-border-image",
296 | "border-image",
297 | "-webkit-border-image-source",
298 | "-moz-border-image-source",
299 | "-o-border-image-source",
300 | "border-image-source",
301 | "-webkit-border-image-slice",
302 | "-moz-border-image-slice",
303 | "-o-border-image-slice",
304 | "border-image-slice",
305 | "-webkit-border-image-width",
306 | "-moz-border-image-width",
307 | "-o-border-image-width",
308 | "border-image-width",
309 | "-webkit-border-image-outset",
310 | "-moz-border-image-outset",
311 | "-o-border-image-outset",
312 | "border-image-outset",
313 | "-webkit-border-image-repeat",
314 | "-moz-border-image-repeat",
315 | "-o-border-image-repeat",
316 | "border-image-repeat",
317 | "outline",
318 | "outline-width",
319 | "outline-style",
320 | "outline-color",
321 | "outline-offset",
322 | "-webkit-box-shadow",
323 | "-moz-box-shadow",
324 | "box-shadow",
325 | "-webkit-transform",
326 | "-moz-transform",
327 | "-ms-transform",
328 | "-o-transform",
329 | "transform",
330 | "-webkit-transform-origin",
331 | "-moz-transform-origin",
332 | "-ms-transform-origin",
333 | "-o-transform-origin",
334 | "transform-origin",
335 | "-webkit-backface-visibility",
336 | "-moz-backface-visibility",
337 | "backface-visibility",
338 | "-webkit-perspective",
339 | "-moz-perspective",
340 | "perspective",
341 | "-webkit-perspective-origin",
342 | "-moz-perspective-origin",
343 | "perspective-origin",
344 | "-webkit-transform-style",
345 | "-moz-transform-style",
346 | "transform-style",
347 | "visibility",
348 | "cursor",
349 | "opacity",
350 | "-webkit-filter",
351 | "filter"
352 | ],
353 | [
354 | "-webkit-transition",
355 | "-moz-transition",
356 | "-ms-transition",
357 | "-o-transition",
358 | "transition",
359 | "-webkit-transition-delay",
360 | "-moz-transition-delay",
361 | "-ms-transition-delay",
362 | "-o-transition-delay",
363 | "transition-delay",
364 | "-webkit-transition-timing-function",
365 | "-moz-transition-timing-function",
366 | "-ms-transition-timing-function",
367 | "-o-transition-timing-function",
368 | "transition-timing-function",
369 | "-webkit-transition-duration",
370 | "-moz-transition-duration",
371 | "-ms-transition-duration",
372 | "-o-transition-duration",
373 | "transition-duration",
374 | "-webkit-transition-property",
375 | "-moz-transition-property",
376 | "-ms-transition-property",
377 | "-o-transition-property",
378 | "transition-property",
379 | "-webkit-animation",
380 | "-moz-animation",
381 | "-ms-animation",
382 | "-o-animation",
383 | "animation",
384 | "-webkit-animation-name",
385 | "-moz-animation-name",
386 | "-ms-animation-name",
387 | "-o-animation-name",
388 | "animation-name",
389 | "-webkit-animation-duration",
390 | "-moz-animation-duration",
391 | "-ms-animation-duration",
392 | "-o-animation-duration",
393 | "animation-duration",
394 | "-webkit-animation-play-state",
395 | "-moz-animation-play-state",
396 | "-ms-animation-play-state",
397 | "-o-animation-play-state",
398 | "animation-play-state",
399 | "-webkit-animation-timing-function",
400 | "-moz-animation-timing-function",
401 | "-ms-animation-timing-function",
402 | "-o-animation-timing-function",
403 | "animation-timing-function",
404 | "-webkit-animation-delay",
405 | "-moz-animation-delay",
406 | "-ms-animation-delay",
407 | "-o-animation-delay",
408 | "animation-delay",
409 | "-webkit-animation-iteration-count",
410 | "-moz-animation-iteration-count",
411 | "-ms-animation-iteration-count",
412 | "-o-animation-iteration-count",
413 | "animation-iteration-count",
414 | "-webkit-animation-direction",
415 | "-moz-animation-direction",
416 | "-ms-animation-direction",
417 | "-o-animation-direction",
418 | "animation-direction"
419 | ],
420 | [
421 | "quotes",
422 | "counter-reset",
423 | "counter-increment",
424 | "resize",
425 | "-webkit-user-select",
426 | "-moz-user-select",
427 | "-ms-user-select",
428 | "user-select",
429 | "nav-index",
430 | "nav-up",
431 | "nav-right",
432 | "nav-down",
433 | "nav-left",
434 | "pointer-events",
435 | "will-change",
436 | "clip",
437 | "-webkit-clip-path",
438 | "clip-path",
439 | "zoom"
440 | ]
441 | ]
442 | }
443 |
--------------------------------------------------------------------------------
/eslintconfig.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | rules: {
3 | // ECMAScript 6
4 | // http://eslint.org/docs/rules/#ecmascript-6
5 | // ------------------------------------------
6 | 'no-cond-assign': 'error', // eslint:recommended
7 | 'no-irregular-whitespace': 'error', // eslint:recommended
8 | 'no-unexpected-multiline': 'error', // eslint:recommended
9 | 'valid-jsdoc': ['error', {
10 | requireParamDescription: false,
11 | requireReturnDescription: false,
12 | requireReturn: false,
13 | prefer: {returns: 'return'},
14 | }],
15 |
16 | 'no-console': 'error',
17 | 'no-constant-condition': 'error',
18 | 'no-control-regex': 'error',
19 | 'no-debugger': 'error',
20 | 'no-dupe-keys': 'error',
21 | 'no-dupe-args': 'error',
22 | 'no-duplicate-case': 'error',
23 | 'no-empty': 'error',
24 | 'no-empty-character-class': 'error',
25 | 'no-ex-assign': 'error',
26 | 'no-extra-boolean-cast': 'error',
27 | 'no-extra-semi': 'error',
28 | 'no-func-assign': 'error',
29 | 'no-inner-declarations': ['error', 'functions'],
30 | 'no-invalid-regexp': 'error',
31 | 'no-unsafe-negation': 'error',
32 | 'no-obj-calls': 'error',
33 | 'no-regex-spaces': 'error',
34 | 'no-sparse-arrays': 'error',
35 | 'no-unreachable': 'error',
36 | 'use-isnan': 'error',
37 | 'valid-typeof': 'error',
38 |
39 | // Best Practices
40 | // http://eslint.org/docs/rules/#best-practices
41 | // --------------------------------------------
42 |
43 | 'guard-for-in': 'error',
44 | 'max-nested-callbacks': ['error', {max: 3}],
45 | 'no-caller': 'error',
46 | 'no-extend-native': 'error',
47 | 'no-extra-bind': 'error',
48 | 'no-invalid-this': 'error',
49 | 'no-multi-spaces': 'error',
50 | 'no-multi-str': 'error',
51 | 'no-new-wrappers': 'error',
52 | 'no-throw-literal': 'error', // eslint:recommended
53 | 'no-with': 'error',
54 | 'consistent-return': 'error',
55 | 'curly': ['error', 'all'],
56 | 'eqeqeq': 'error',
57 | 'no-alert': 'error',
58 | 'no-eval': 'error',
59 | 'no-fallthrough': 'error',
60 | 'no-floating-decimal': 'error',
61 | 'no-implied-eval': 'error',
62 | 'no-iterator': 'error',
63 | 'no-labels': 'error',
64 | 'no-lone-blocks': 'error',
65 | 'no-global-assign': 'error',
66 | 'no-new': 'error',
67 | 'no-new-func': 'error',
68 | 'no-octal': 'error', // default
69 | 'no-octal-escape': 'error',
70 | 'no-proto': 'error',
71 | 'no-redeclare': 'error', // default
72 | 'no-return-assign': 'error',
73 | 'no-script-url': 'error',
74 | 'no-sequences': 'error',
75 | 'no-unused-expressions': 'error',
76 | 'radix': 'error',
77 |
78 | // Variables
79 | // http://eslint.org/docs/rules/#variables
80 | // ---------------------------------------
81 | 'no-unused-vars': ['error', {args: 'after-used', argsIgnorePattern: '^_'}], // check that all args are used¬
82 | 'no-delete-var': 'error', // eslint:recommended
83 | 'no-label-var': 'error',
84 | 'no-shadow': 'error',
85 | 'no-shadow-restricted-names': 'error',
86 | 'no-undef': 'error', // default
87 | 'no-undef-init': 'error',
88 | 'no-undefined': 'error',
89 |
90 | // Node.js and CommonJS
91 | // http://eslint.org/docs/rules/#nodejs-and-commonjs
92 | // -------------------------------------------------
93 | 'no-process-exit': 'error',
94 |
95 | // Stylistic Issues
96 | // http://eslint.org/docs/rules/#stylistic-issues
97 | // ----------------------------------------------
98 | 'indent': ['error', 2, {
99 | SwitchCase: 1,
100 | // continuation indent
101 | VariableDeclarator: 1, // indent is multiplier * indent = 1 * 2
102 | MemberExpression: 2, // indent is multiplier * indent = 2 * 2
103 | FunctionDeclaration: {parameters: 2},
104 | FunctionExpression: {parameters: 2},
105 | CallExpression: {arguments: 2},
106 | }],
107 | 'block-spacing': ['error', 'always'],
108 | 'array-bracket-spacing': ['error', 'never'],
109 | 'brace-style': 'error',
110 | 'camelcase': 'error',
111 | 'comma-spacing': 'error',
112 | 'comma-style': 'error',
113 | 'computed-property-spacing': 'error',
114 | 'eol-last': 'error',
115 | 'func-call-spacing': 'error',
116 | 'key-spacing': 'error',
117 | 'keyword-spacing': 'error',
118 | 'linebreak-style': 'off', // check this in git
119 | 'new-cap': 'error',
120 | 'no-array-constructor': 'error',
121 | 'no-mixed-spaces-and-tabs': 'error', // eslint:recommended
122 | 'no-multiple-empty-lines': ['error', {max: 2}],
123 | 'no-new-object': 'error',
124 | 'no-trailing-spaces': 'error',
125 | 'object-curly-spacing': 'error',
126 | 'one-var': ['error', {
127 | var: 'never',
128 | let: 'never',
129 | const: 'never',
130 | }],
131 | 'padded-blocks': ['off', 'never'],
132 | 'quote-props': ['error', 'consistent'],
133 | 'semi-spacing': 'error',
134 | 'semi': 'error',
135 | 'space-in-parens': ['error', 'never'],
136 | 'space-before-blocks': 'error',
137 | 'space-before-function-paren': ['error', {named: 'never', anonymous: 'always'}],
138 | 'spaced-comment': ['error', 'always'],
139 | 'unicode-bom': 'warn',
140 | 'new-parens': 'error',
141 | 'no-nested-ternary': 'error',
142 | 'space-infix-ops': 'error',
143 | 'space-unary-ops': ['error', {words: true, nonwords: false}],
144 | 'yoda': ['error', 'never'],
145 |
146 | 'arrow-parens': ['error', 'always'],
147 |
148 | // parens are optional but recommended.
149 | // ESLint doesn't support a *consistent*
150 | // setting so "always" is used.
151 | 'constructor-super': 'error', // eslint:recommended
152 | 'generator-star-spacing': ['error', 'after'],
153 | 'no-new-symbol': 'error', // eslint:recommended
154 | 'no-this-before-super': 'error', // eslint:recommended
155 | 'no-var': 'error',
156 | 'prefer-rest-params': 'error',
157 | 'prefer-spread': 'error',
158 | 'rest-spread-spacing': 'error',
159 | 'yield-star-spacing': ['error', 'after'],
160 | 'object-shorthand': ['error', 'always', {'avoidQuotes': true}],
161 |
162 | 'comma-dangle': ['error', {
163 | 'arrays': 'never',
164 | 'objects': 'always-multiline',
165 | 'imports': 'never',
166 | 'exports': 'never',
167 | 'functions': 'never',
168 | }],
169 | 'quotes': ['error', 'single'],
170 | },
171 | };
172 |
--------------------------------------------------------------------------------
/gulp/compileHtml.mjs:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import fileinclude from 'gulp-file-include';
3 | import htmlbeautify from 'gulp-html-beautify';
4 |
5 | const compileHtml = () => {
6 | return gulp.src(['source/html/*.html'])
7 | .pipe(fileinclude({
8 | prefix: '@@',
9 | basepath: '@root',
10 | context: { // глобальные переменные для include
11 | test: 'text',
12 | },
13 | }))
14 | .pipe(htmlbeautify({
15 | 'indent_size': 2,
16 | 'preserve_newlines': true,
17 | 'max_preserve_newlines': 0,
18 | 'wrap_attributes': 'auto',
19 | }))
20 | .pipe(gulp.dest('build'));
21 | };
22 |
23 | export default compileHtml;
24 |
--------------------------------------------------------------------------------
/gulp/compileScripts.mjs:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import webpackStream from 'webpack-stream';
3 | import webpackConfig from '../webpack.config.cjs';
4 |
5 | const compileScripts = () =>
6 | gulp.src(['source/js/main.js'])
7 | .pipe(webpackStream(webpackConfig))
8 | .pipe(gulp.dest('build/js'));
9 |
10 | export default compileScripts;
11 |
--------------------------------------------------------------------------------
/gulp/compileStyles.mjs:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import dartSass from 'sass';
3 | import gulpSass from 'gulp-sass';
4 | import postcss from 'gulp-postcss';
5 | import autoprefixer from 'autoprefixer';
6 | import csso from 'gulp-csso';
7 | import gcmq from 'gulp-group-css-media-queries';
8 | import rename from 'gulp-rename';
9 |
10 | const sass = gulpSass(dartSass);
11 |
12 | const compileStyles = () =>
13 | gulp.src('source/sass/style.scss', {sourcemaps: true})
14 | .pipe(sass().on('error', sass.logError))
15 | .pipe(postcss([autoprefixer({
16 | grid: true,
17 | })]))
18 | .pipe(gcmq()) // выключите, если в проект импортятся шрифты через ссылку на внешний источник
19 | .pipe(gulp.dest('build/css'))
20 | .pipe(csso())
21 | .pipe(rename('style.min.css'))
22 | .pipe(gulp.dest('build/css', {sourcemaps: '.'}));
23 |
24 | export default compileStyles;
25 |
--------------------------------------------------------------------------------
/gulp/copyAssets.mjs:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 |
3 | const copySvg = () =>
4 | gulp.src('source/img/**/*.svg', {base: 'source'})
5 | .pipe(gulp.dest('build'));
6 |
7 | const copyImages = () =>
8 | gulp.src('source/img/**/*.{png,jpg,webp}', {base: 'source'})
9 | .pipe(gulp.dest('build'));
10 |
11 | const copy = () =>
12 | gulp.src([
13 | 'source/**.html',
14 | 'source/fonts/**',
15 | 'source/img/**',
16 | 'source/favicon/**'
17 | ], {
18 | base: 'source',
19 | })
20 | .pipe(gulp.dest('build'));
21 |
22 | export {copy, copyImages, copySvg};
23 |
--------------------------------------------------------------------------------
/gulp/optimizeImages.mjs:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import rename from 'gulp-rename';
3 | import imagemin from 'gulp-imagemin';
4 | import webp from 'gulp-webp';
5 | import svgstore from 'gulp-svgstore';
6 |
7 | const svgo = () =>
8 | gulp
9 | .src('source/img/**/*.{svg}')
10 | .pipe(
11 | imagemin([
12 | imagemin.svgo({
13 | plugins: [
14 | {removeViewBox: false},
15 | {removeRasterImages: true},
16 | {removeUselessStrokeAndFill: false}
17 | ],
18 | })
19 | ])
20 | )
21 | .pipe(gulp.dest('source/img'));
22 |
23 | const sprite = () =>
24 | gulp
25 | .src('source/img/sprite/*.svg')
26 | .pipe(svgstore({inlineSvg: true}))
27 | .pipe(rename('sprite_auto.svg'))
28 | .pipe(gulp.dest('build/img'));
29 |
30 | /*
31 | Optional tasks
32 | ---------------------------------
33 |
34 | Используйте отличное от дефолтного значение root, если нужно обработать отдельную папку в img,
35 | а не все изображения в img во всех папках.
36 |
37 | root = '' - по дефолту webp добавляются и обновляются во всех папках в source/img/
38 | root = 'content/' - webp добавляются и обновляются только в source/img/content/
39 | */
40 |
41 | const createWebp = () => {
42 | const root = '';
43 | return gulp
44 | .src(`source/img/${root}**/*.{png,jpg}`)
45 | .pipe(webp({quality: 90}))
46 | .pipe(gulp.dest(`source/img/${root}`));
47 | };
48 |
49 | const optimizeImages = () =>
50 | gulp
51 | .src('build/img/**/*.{png,jpg}')
52 | .pipe(
53 | imagemin([
54 | imagemin.optipng({optimizationLevel: 3}),
55 | imagemin.mozjpeg({quality: 75, progressive: true})
56 | ])
57 | )
58 | .pipe(gulp.dest('build/img'));
59 |
60 | export {svgo, sprite, createWebp, optimizeImages};
61 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import browserSync from 'browser-sync';
3 | import del from 'del';
4 | import styles from './gulp/compileStyles.mjs';
5 | import { copy, copyImages, copySvg } from './gulp/copyAssets.mjs';
6 | import js from './gulp/compileScripts.mjs';
7 | import { svgo, sprite, createWebp, optimizeImages } from './gulp/optimizeImages.mjs';
8 | import html from './gulp/compileHtml.mjs';
9 |
10 | const server = browserSync.create();
11 | const streamStyles = () => styles().pipe(server.stream());
12 | const clean = () => del('build');
13 | const refresh = (done) => {
14 | server.reload();
15 | done();
16 | };
17 |
18 |
19 | const syncServer = () => {
20 | server.init({
21 | server: 'build/',
22 | index: 'sitemap.html',
23 | notify: false,
24 | open: true,
25 | cors: true,
26 | ui: false,
27 | });
28 |
29 | gulp.watch('source/pug/**/*.pug', gulp.series(html, refresh));
30 | gulp.watch('source/sass/**/*.{scss,sass}', streamStyles);
31 | gulp.watch('source/js/**/*.{js,json}', gulp.series(js, refresh));
32 | gulp.watch('source/data/**/*.{js,json}', gulp.series(copy, refresh));
33 | gulp.watch('source/img/**/*.svg', gulp.series(copySvg, sprite, html, refresh));
34 | gulp.watch('source/img/**/*.{png,jpg,webp}', gulp.series(copyImages, html, refresh));
35 |
36 | gulp.watch('source/favicon/**', gulp.series(copy, refresh));
37 | gulp.watch('source/video/**', gulp.series(copy, refresh));
38 | gulp.watch('source/downloads/**', gulp.series(copy, refresh));
39 | gulp.watch('source/*.php', gulp.series(copy, refresh));
40 | };
41 |
42 |
43 | const build = gulp.series(clean, svgo, copy, styles, sprite, js, html);
44 | const start = gulp.series(build, syncServer);
45 |
46 | export { optimizeImages as imagemin, createWebp as webp, build, start };
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "type": "module",
4 | "browserslist": [
5 | "last 3 versions",
6 | "IE 11",
7 | "Firefox ESR"
8 | ],
9 | "scripts": {
10 | "editorconfig": "editorconfig-cli",
11 | "stylelint": "stylelint \"source/sass/**/*.scss\" --syntax scss",
12 | "test": "npm run editorconfig & npm run stylelint & eslint source/js/",
13 | "build": "cross-env NODE_ENV=production gulp build & gulp imagemin",
14 | "start": "cross-env NODE_ENV=development gulp start",
15 | "imagemin": "gulp imagemin",
16 | "webp": "gulp webp"
17 | },
18 | "editorconfig-cli": [
19 | "*.json",
20 | "*.js",
21 | "source/**/*.html",
22 | "source/**/*.pug",
23 | "source/js/**/*.js",
24 | "source/img/**/*.svg",
25 | "source/sass/**/*.{sass,scss}"
26 | ],
27 | "devDependencies": {
28 | "@babel/preset-env": "7.18.6",
29 | "babel-loader": "8.2.5",
30 | "@htmlacademy/editorconfig-cli": "1.x",
31 | "eslint": "7.32.0",
32 | "stylelint": "13.13.1",
33 | "autoprefixer": "10.4.7",
34 | "browser-sync": "2.27.10",
35 | "cross-env": "7.0.3",
36 | "del": "6.1.1",
37 | "gulp": "4.0.2",
38 | "gulp-csso": "4.0.1",
39 | "gulp-file-include": "2.3.0",
40 | "gulp-group-css-media-queries": "1.2.2",
41 | "gulp-html-beautify": "1.0.1",
42 | "gulp-imagemin": "7.1.0",
43 | "gulp-plumber": "1.2.1",
44 | "gulp-postcss": "9.0.1",
45 | "gulp-rename": "1.4.0",
46 | "gulp-sass": "5.1.0",
47 | "gulp-svgstore": "7.0.1",
48 | "gulp-webp": "4.0.1",
49 | "postcss": "8.4.14",
50 | "sass": "1.53.0",
51 | "webpack": "4.42.0",
52 | "webpack-stream": "5.2.1",
53 | "circular-dependency-plugin": "5.2.2",
54 | "clean-webpack-plugin": "3.0.0",
55 | "duplicate-package-checker-webpack-plugin": "3.0.0"
56 | },
57 | "engines": {
58 | "node": ">=16.15.1"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/source/fonts/rouble.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htmlonelove/liga-basic-template/910bea7921f8f930076ec36812bf6392f6c54d04/source/fonts/rouble.woff
--------------------------------------------------------------------------------
/source/fonts/rouble.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htmlonelove/liga-basic-template/910bea7921f8f930076ec36812bf6392f6c54d04/source/fonts/rouble.woff2
--------------------------------------------------------------------------------
/source/html/base/footer.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/source/html/base/head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/source/html/base/header.html:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/source/html/base/modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @@include("source/html/components/modal-@@name.html")
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/source/html/components.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | @@include("source/html/base/head.html")
6 | Компоненты
7 |
8 |
52 |
53 |
54 |
55 |
56 |
57 | @@include("build/img/sprite_auto.svg")
58 |
59 |
60 |
61 | @@include("source/html/base/header.html")
62 |
63 |
64 |
65 |
66 | ←
67 | вернуться на разводящую
68 |
69 |
Компоненты, используемые в вёрстке
70 |
71 |
72 | Название компонента или группы компонентов
73 |
74 | Класс компонента \ модификаторы
75 | При необходимости, подсказки \ предупреждение \ важная информация
76 | Компонент
77 |
78 |
79 |
80 |
85 |
86 |
87 |
88 | @@include("source/html/base/footer.html")
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/source/html/components/modal-feedback.html:
--------------------------------------------------------------------------------
1 | Обычная модалка
2 |
--------------------------------------------------------------------------------
/source/html/components/modal-success.html:
--------------------------------------------------------------------------------
1 | Размер этой модалки зависит от ширины контента, отключена анимация-скейл и передан модификатор `some-mod`
2 |
--------------------------------------------------------------------------------
/source/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | @@include("source/html/base/head.html")
6 | ProjectName — Главная
7 |
8 |
9 |
10 |
11 |
12 | @@include("build/img/sprite_auto.svg")
13 |
14 |
15 |
16 | <-- для внутренних страниц используйте "headerActiveLinkIndex": 0\1\2 -->
17 | @@include("source/html/base/header.html", {
18 | "index": true
19 | })
20 |
21 |
22 |
26 |
27 |
28 | @@include("source/html/base/footer.html")
29 |
30 | @@include("source/html/base/modal.html", {
31 | "name": "feedback"
32 | })
33 |
34 | @@include("source/html/base/modal.html", {
35 | "name": "success",
36 | "fitContent": true,
37 | "noScale": true,
38 | "mod": "some-mod"
39 | })
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/source/html/sitemap.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | @@include("source/html/base/head.html")
6 | Список страниц
7 |
8 |
52 |
53 |
54 |
55 |
56 |
57 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/source/img/bg/placeholder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htmlonelove/liga-basic-template/910bea7921f8f930076ec36812bf6392f6c54d04/source/img/bg/placeholder.jpg
--------------------------------------------------------------------------------
/source/img/content/placeholder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htmlonelove/liga-basic-template/910bea7921f8f930076ec36812bf6392f6c54d04/source/img/content/placeholder.jpg
--------------------------------------------------------------------------------
/source/img/slides/placeholder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htmlonelove/liga-basic-template/910bea7921f8f930076ec36812bf6392f6c54d04/source/img/slides/placeholder.jpg
--------------------------------------------------------------------------------
/source/img/sprite/icon-close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/img/svg/not-needed-in-sprite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/js/main.js:
--------------------------------------------------------------------------------
1 | import {iosVhFix} from './utils/ios-vh-fix';
2 | import {initModals} from './modules/modals/init-modals';
3 |
4 | // ---------------------------------
5 |
6 | window.addEventListener('DOMContentLoaded', () => {
7 |
8 | // Utils
9 | // ---------------------------------
10 |
11 | iosVhFix();
12 |
13 | // Modules
14 | // ---------------------------------
15 |
16 | // все скрипты должны быть в обработчике 'DOMContentLoaded', но не все в 'load'
17 | // в load следует добавить скрипты, не участвующие в работе первого экрана
18 | window.addEventListener('load', () => {
19 | initModals();
20 | });
21 | });
22 |
23 | // ---------------------------------
24 |
25 | // ❗❗❗ обязательно установите плагины eslint, stylelint, editorconfig в редактор кода.
26 |
27 | // привязывайте js не на классы, а на дата атрибуты (data-validate)
28 |
29 | // вместо модификаторов .block--active используем утилитарные классы
30 | // .is-active || .is-open || .is-invalid и прочие (обязателен нейминг в два слова)
31 | // .select.select--opened ❌ ---> [data-select].is-open ✅
32 |
33 | // выносим все в дата атрибуты
34 | // url до иконок пинов карты, настройки автопрокрутки слайдера, url к json и т.д.
35 |
36 | // для адаптивного JS используейтся matchMedia и addListener
37 | // const breakpoint = window.matchMedia(`(min-width:1024px)`);
38 | // const breakpointChecker = () => {
39 | // if (breakpoint.matches) {
40 | // } else {
41 | // }
42 | // };
43 | // breakpoint.addListener(breakpointChecker);
44 | // breakpointChecker();
45 |
46 | // используйте .closest(el)
47 |
--------------------------------------------------------------------------------
/source/js/modules/modals/init-modals.js:
--------------------------------------------------------------------------------
1 | import {Modals} from './modals';
2 |
3 | let modals;
4 |
5 | // Здесь реализован пример открытия модалки через колбэк закрытия
6 | // const openModalInCloseCallback = (name, context = this) => {
7 | // context._enableScrolling = false;
8 | // context._setSettings('default');
9 | // modals.open(name);
10 | // };
11 |
12 | // closeCallback() {
13 | // openModalInCloseCallback('modal-5');
14 | // },
15 |
16 | const settings = {
17 | 'default': {
18 | preventDefault: true,
19 | stopPlay: true,
20 | lockFocus: true,
21 | startFocus: true,
22 | focusBack: true,
23 | eventTimeout: 400,
24 | openCallback: false,
25 | closeCallback: false,
26 | },
27 | };
28 |
29 | const initModals = () => {
30 | const modalElements = document.querySelectorAll('.modal');
31 | if (modalElements.length) {
32 | modalElements.forEach((el) => {
33 | setTimeout(() => {
34 | el.classList.remove('modal--preload');
35 | }, 100);
36 | });
37 | }
38 |
39 | modals = new Modals(settings);
40 | // Используйте в разработке экспортируемую переменную modals, window сделан для бэкэнда
41 | window.modals = modals;
42 | };
43 |
44 | export {modals, initModals};
45 |
--------------------------------------------------------------------------------
/source/js/modules/modals/modals.js:
--------------------------------------------------------------------------------
1 | import {ScrollLock} from '../../utils/scroll-lock';
2 | import {FocusLock} from '../../utils/focus-lock';
3 |
4 | export class Modals {
5 | constructor(settings = {}) {
6 | this._scrollLock = new ScrollLock();
7 | this._focusLock = new FocusLock();
8 |
9 | this._modalOpenElements = document.querySelectorAll('[data-open-modal]');
10 | this._openedModalElement = null;
11 | this._modalName = null;
12 | this._enableScrolling = true;
13 | this._settingKey = 'default';
14 |
15 | this._settings = settings;
16 | this._preventDefault = this._settings[this._settingKey].preventDefault;
17 | this._stopPlay = this._settings[this._settingKey].stopPlay;
18 | this._lockFocus = this._settings[this._settingKey].lockFocus;
19 | this._startFocus = this._settings[this._settingKey].startFocus;
20 | this._focusBack = this._settings[this._settingKey].focusBack;
21 | this._eventTimeout = this._settings[this._settingKey].eventTimeout;
22 | this._openCallback = this._settings[this._settingKey].openCallback;
23 | this._closeCallback = this._settings[this._settingKey].closeCallback;
24 |
25 | this._documentKeydownHandler = this._documentKeydownHandler.bind(this);
26 | this._documentClickHandler = this._documentClickHandler.bind(this);
27 | this._modalClickHandler = this._modalClickHandler.bind(this);
28 |
29 | this._init();
30 | }
31 |
32 | _init() {
33 | if (this._modalOpenElements.length) {
34 | document.addEventListener('click', this._documentClickHandler);
35 | }
36 | }
37 |
38 | _setSettings(settingKey = this._settingKey) {
39 | if (!this._settings[settingKey]) {
40 | return;
41 | }
42 |
43 | this._preventDefault =
44 | typeof this._settings[settingKey].preventDefault === 'boolean'
45 | ? this._settings[settingKey].preventDefault
46 | : this._settings[this._settingKey].preventDefault;
47 | this._stopPlay =
48 | typeof this._settings[settingKey].stopPlay === 'boolean'
49 | ? this._settings[settingKey].stopPlay
50 | : this._settings[this._settingKey].stopPlay;
51 | this._lockFocus =
52 | typeof this._settings[settingKey].lockFocus === 'boolean'
53 | ? this._settings[settingKey].lockFocus
54 | : this._settings[this._settingKey].lockFocus;
55 | this._startFocus =
56 | typeof this._settings[settingKey].startFocus === 'boolean'
57 | ? this._settings[settingKey].startFocus
58 | : this._settings[this._settingKey].startFocus;
59 | this._focusBack =
60 | typeof this._settings[settingKey].lockFocus === 'boolean'
61 | ? this._settings[settingKey].focusBack
62 | : this._settings[this._settingKey].focusBack;
63 | this._eventTimeout =
64 | typeof this._settings[settingKey].eventTimeout === 'number'
65 | ? this._settings[settingKey].eventTimeout
66 | : this._settings[this._settingKey].eventTimeout;
67 | this._openCallback = this._settings[settingKey].openCallback || this._settings[this._settingKey].openCallback;
68 | this._closeCallback = this._settings[settingKey].closeCallback || this._settings[this._settingKey].closeCallback;
69 | }
70 |
71 | _documentClickHandler(evt) {
72 | const target = evt.target;
73 |
74 | if (!target.closest('[data-open-modal]')) {
75 | return;
76 | }
77 |
78 | evt.preventDefault();
79 |
80 | this._modalName = target.closest('[data-open-modal]').dataset.openModal;
81 |
82 | if (!this._modalName) {
83 | return;
84 | }
85 |
86 | this.open();
87 | }
88 |
89 | _documentKeydownHandler(evt) {
90 | const isEscKey = evt.key === 'Escape' || evt.key === 'Esc';
91 |
92 | if (isEscKey) {
93 | evt.preventDefault();
94 | this.close(document.querySelector('.modal.is-active').dataset.modal);
95 | }
96 | }
97 |
98 | _modalClickHandler(evt) {
99 | const target = evt.target;
100 |
101 | if (!target.closest('[data-close-modal]')) {
102 | return;
103 | }
104 |
105 | this.close(target.closest('[data-modal]').dataset.modal);
106 | }
107 |
108 | _addListeners(modal) {
109 | modal.addEventListener('click', this._modalClickHandler);
110 | document.addEventListener('keydown', this._documentKeydownHandler);
111 | }
112 |
113 | _removeListeners(modal) {
114 | modal.removeEventListener('click', this._modalClickHandler);
115 | document.removeEventListener('keydown', this._documentKeydownHandler);
116 | }
117 |
118 | _stopInteractive(modal) {
119 | if (this._stopPlay) {
120 | modal.querySelectorAll('video, audio').forEach((el) => el.pause());
121 | modal.querySelectorAll('[data-iframe]').forEach((el) => {
122 | el.querySelector('iframe').contentWindow.postMessage('{"event": "command", "func": "pauseVideo", "args": ""}', '*');
123 | });
124 | }
125 | }
126 |
127 | _autoPlay(modal) {
128 | modal.querySelectorAll('[data-iframe]').forEach((el) => {
129 | const autoPlay = el.closest('[data-auto-play]');
130 | if (autoPlay) {
131 | el.querySelector('iframe').contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
132 | }
133 | });
134 | }
135 |
136 | open(modalName = this._modalName) {
137 | const modal = document.querySelector(`[data-modal="${modalName}"]`);
138 |
139 | if (!modal || modal.classList.contains('is-active')) {
140 | return;
141 | }
142 |
143 | document.removeEventListener('click', this._documentClickHandler);
144 |
145 | this._openedModalElement = document.querySelector('.modal.is-active');
146 |
147 | if (this._openedModalElement) {
148 | this._enableScrolling = false;
149 | this.close(this._openedModalElement.dataset.modal);
150 | }
151 |
152 | this._setSettings(modalName);
153 | modal.classList.add('is-active');
154 |
155 | if (!this._openedModalElement) {
156 | this._scrollLock.disableScrolling();
157 | }
158 |
159 | if (this._openCallback) {
160 | this._openCallback();
161 | }
162 |
163 | if (this._lockFocus) {
164 | this._focusLock.lock('.modal.is-active', this._startFocus);
165 | }
166 |
167 | setTimeout(() => {
168 | this._addListeners(modal);
169 | this._autoPlay(modal);
170 | document.addEventListener('click', this._documentClickHandler);
171 | }, this._eventTimeout);
172 | }
173 |
174 | close(modalName = this._modalName) {
175 | const modal = document.querySelector(`[data-modal="${modalName}"]`);
176 | document.removeEventListener('click', this._documentClickHandler);
177 |
178 | if (!modal || !modal.classList.contains('is-active')) {
179 | return;
180 | }
181 |
182 | if (this._lockFocus) {
183 | this._focusLock.unlock(this._focusBack);
184 | }
185 |
186 | modal.classList.remove('is-active');
187 | this._removeListeners(modal);
188 | this._stopInteractive(modal);
189 |
190 | if (this._closeCallback) {
191 | this._closeCallback();
192 | }
193 |
194 | if (this._enableScrolling) {
195 | setTimeout(() => {
196 | this._scrollLock.enableScrolling();
197 | }, this._eventTimeout);
198 | }
199 |
200 | setTimeout(() => {
201 | document.addEventListener('click', this._documentClickHandler);
202 | }, this._eventTimeout);
203 |
204 | this._setSettings('default');
205 | this._enableScrolling = true;
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/source/js/utils/focus-lock.js:
--------------------------------------------------------------------------------
1 | const SELECTORS = [
2 | 'a[href]',
3 | 'area[href]',
4 | 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',
5 | 'select:not([disabled]):not([aria-hidden])',
6 | 'textarea:not([disabled]):not([aria-hidden])',
7 | 'button:not([disabled]):not([aria-hidden])',
8 | 'iframe',
9 | 'object',
10 | 'embed',
11 | '[contenteditable]',
12 | '[tabindex]:not([tabindex^="-"])'
13 | ];
14 |
15 | export class FocusLock {
16 | constructor() {
17 | this._lockedSelector = null;
18 | this._focusableElements = null;
19 | this._endElement = null;
20 | this._selectors = SELECTORS;
21 |
22 | this._documentKeydownHandler = this._documentKeydownHandler.bind(this);
23 | }
24 |
25 | _documentKeydownHandler(evt) {
26 | const activeElement = document.activeElement;
27 | if (evt.key === 'Tab') {
28 | if (!this._focusableElements.length) {
29 | evt.preventDefault();
30 | activeElement.blur();
31 | return;
32 | }
33 | if (this._focusableElements.length === 1) {
34 | evt.preventDefault();
35 | this._focusableElements[0].focus();
36 | return;
37 | }
38 | if (this._focusableElements.length > 1 && !activeElement.closest(this._lockedSelector)) {
39 | evt.preventDefault();
40 | this._focusableElements[0].focus();
41 | return;
42 | }
43 | }
44 | if (evt.key === 'Tab' && !evt.shiftKey && activeElement === this._focusableElements[this._focusableElements.length - 1]) {
45 | evt.preventDefault();
46 | this._focusableElements[0].focus();
47 | }
48 | if (evt.key === 'Tab' && evt.shiftKey && activeElement === this._focusableElements[0]) {
49 | evt.preventDefault();
50 | this._focusableElements[this._focusableElements.length - 1].focus();
51 | }
52 | }
53 |
54 | lock(lockedSelector, startFocus = true) {
55 | this.unlock();
56 | this._lockedSelector = lockedSelector;
57 | const lockedElement = document.querySelector(this._lockedSelector);
58 | if (!lockedElement) {
59 | return;
60 | }
61 | this._focusableElements = lockedElement.querySelectorAll(this._selectors);
62 | this._endElement = document.activeElement;
63 | const startElement = lockedElement.querySelector('[data-focus]') || this._focusableElements[0];
64 | if (this._endElement) {
65 | this._endElement.blur();
66 | }
67 | if (startElement && startFocus) {
68 | startElement.focus();
69 | }
70 | document.addEventListener('keydown', this._documentKeydownHandler);
71 | }
72 |
73 | unlock(returnFocus = true) {
74 | if (this._endElement && returnFocus) {
75 | this._endElement.focus();
76 | }
77 | this._lockedSelector = null;
78 | this._focusableElements = null;
79 | this._endElement = null;
80 | document.removeEventListener('keydown', this._documentKeydownHandler);
81 | }
82 | }
83 |
84 | window.focusLock = new FocusLock();
85 |
--------------------------------------------------------------------------------
/source/js/utils/ios-checker.js:
--------------------------------------------------------------------------------
1 | export const iosChecker = () => {
2 | return [
3 | 'iPad Simulator',
4 | 'iPhone Simulator',
5 | 'iPod Simulator',
6 | 'iPad',
7 | 'iPhone',
8 | 'iPod'
9 | ].includes(navigator.platform)
10 | // iPad on iOS 13 detection
11 | || (navigator.userAgent.includes('Mac') && 'ontouchend' in document);
12 | };
13 |
--------------------------------------------------------------------------------
/source/js/utils/ios-vh-fix.js:
--------------------------------------------------------------------------------
1 | import {iosChecker} from './ios-checker';
2 |
3 | const iosVhFix = () => {
4 | if (!(!!window.MSInputMethodContext && !!document.documentMode)) {
5 | if (iosChecker()) {
6 | let vh = window.innerHeight * 0.01;
7 | document.documentElement.style.setProperty('--vh', `${vh}px`);
8 |
9 | window.addEventListener('resize', function () {
10 | vh = window.innerHeight * 0.01;
11 | document.documentElement.style.setProperty('--vh', `${vh}px`);
12 | });
13 | }
14 | }
15 | };
16 |
17 | export {iosVhFix};
18 |
--------------------------------------------------------------------------------
/source/js/utils/scroll-lock.js:
--------------------------------------------------------------------------------
1 | import {iosChecker} from './ios-checker';
2 |
3 | export class ScrollLock {
4 | constructor() {
5 | this._iosChecker = iosChecker;
6 | this._lockClass = this._iosChecker() ? 'scroll-lock-ios' : 'scroll-lock';
7 | this._scrollTop = null;
8 | this._fixedBlockElements = document.querySelectorAll('[data-fix-block]');
9 | }
10 |
11 | _getScrollbarWidth() {
12 | return window.innerWidth - document.documentElement.clientWidth;
13 | }
14 |
15 | _getBodyScrollTop() {
16 | return (
17 | self.pageYOffset ||
18 | (document.documentElement && document.documentElement.ScrollTop) ||
19 | (document.body && document.body.scrollTop)
20 | );
21 | }
22 |
23 | disableScrolling() {
24 | this._scrollTop = document.body.dataset.scroll = document.body.dataset.scroll ? document.body.dataset.scroll : this._getBodyScrollTop();
25 | if (this._getScrollbarWidth()) {
26 | document.body.style.paddingRight = `${this._getScrollbarWidth()}px`;
27 | this._fixedBlockElements.forEach((block) => {
28 | block.style.paddingRight = `${this._getScrollbarWidth()}px`;
29 | });
30 | }
31 | document.body.style.top = `-${this._scrollTop}px`;
32 | document.body.classList.add(this._lockClass);
33 | }
34 |
35 | enableScrolling() {
36 | document.body.classList.remove(this._lockClass);
37 | window.scrollTo(0, +document.body.dataset.scroll);
38 | document.body.style.paddingRight = null;
39 | document.body.style.top = null;
40 | this._fixedBlockElements.forEach((block) => {
41 | block.style.paddingRight = null;
42 | });
43 | document.body.removeAttribute('data-scroll');
44 | this._scrollTop = null;
45 | }
46 | }
47 |
48 | window.scrollLock = new ScrollLock();
49 |
--------------------------------------------------------------------------------
/source/js/vendor.js:
--------------------------------------------------------------------------------
1 | // Swiper 7.4.1
2 | // import './vendor/swiper';
3 | import './vendor/focus-visible-polyfill';
4 |
--------------------------------------------------------------------------------
/source/js/vendor/focus-visible-polyfill.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Applies the :focus-visible polyfill at the given scope.
3 | * A scope in this case is either the top-level Document or a Shadow Root.
4 | *
5 | * @param {(Document|ShadowRoot)} scope
6 | * @see https://github.com/WICG/focus-visible
7 | */
8 | function applyFocusVisiblePolyfill(scope) {
9 | var hadKeyboardEvent = true;
10 | var hadFocusVisibleRecently = false;
11 | var hadFocusVisibleRecentlyTimeout = null;
12 |
13 | var inputTypesAllowlist = {
14 | text: true,
15 | search: true,
16 | url: true,
17 | tel: true,
18 | email: true,
19 | password: true,
20 | number: true,
21 | date: true,
22 | month: true,
23 | week: true,
24 | time: true,
25 | datetime: true,
26 | 'datetime-local': true,
27 | };
28 |
29 | /**
30 | * Helper function for legacy browsers and iframes which sometimes focus
31 | * elements like document, body, and non-interactive SVG.
32 | * @param {Element} el
33 | */
34 | function isValidFocusTarget(el) {
35 | if (
36 | el &&
37 | el !== document &&
38 | el.nodeName !== 'HTML' &&
39 | el.nodeName !== 'BODY' &&
40 | 'classList' in el &&
41 | 'contains' in el.classList
42 | ) {
43 | return true;
44 | }
45 | return false;
46 | }
47 |
48 | /**
49 | * Computes whether the given element should automatically trigger the
50 | * `focus-visible` class being added, i.e. whether it should always match
51 | * `:focus-visible` when focused.
52 | * @param {Element} el
53 | * @return {boolean}
54 | */
55 | function focusTriggersKeyboardModality(el) {
56 | var type = el.type;
57 | var tagName = el.tagName;
58 |
59 | if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {
60 | return true;
61 | }
62 |
63 | if (tagName === 'TEXTAREA' && !el.readOnly) {
64 | return true;
65 | }
66 |
67 | if (el.isContentEditable) {
68 | return true;
69 | }
70 |
71 | return false;
72 | }
73 |
74 | /**
75 | * Add the `focus-visible` class to the given element if it was not added by
76 | * the author.
77 | * @param {Element} el
78 | */
79 | function addFocusVisibleClass(el) {
80 | if (el.classList.contains('focus-visible')) {
81 | return;
82 | }
83 | el.classList.add('focus-visible');
84 | el.setAttribute('data-focus-visible-added', '');
85 | }
86 |
87 | /**
88 | * Remove the `focus-visible` class from the given element if it was not
89 | * originally added by the author.
90 | * @param {Element} el
91 | */
92 | function removeFocusVisibleClass(el) {
93 | if (!el.hasAttribute('data-focus-visible-added')) {
94 | return;
95 | }
96 | el.classList.remove('focus-visible');
97 | el.removeAttribute('data-focus-visible-added');
98 | }
99 |
100 | /**
101 | * If the most recent user interaction was via the keyboard;
102 | * and the key press did not include a meta, alt/option, or control key;
103 | * then the modality is keyboard. Otherwise, the modality is not keyboard.
104 | * Apply `focus-visible` to any current active element and keep track
105 | * of our keyboard modality state with `hadKeyboardEvent`.
106 | * @param {KeyboardEvent} e
107 | */
108 | function onKeyDown(e) {
109 | if (e.metaKey || e.altKey || e.ctrlKey) {
110 | return;
111 | }
112 |
113 | if (isValidFocusTarget(scope.activeElement)) {
114 | addFocusVisibleClass(scope.activeElement);
115 | }
116 |
117 | hadKeyboardEvent = true;
118 | }
119 |
120 | /**
121 | * If at any point a user clicks with a pointing device, ensure that we change
122 | * the modality away from keyboard.
123 | * This avoids the situation where a user presses a key on an already focused
124 | * element, and then clicks on a different element, focusing it with a
125 | * pointing device, while we still think we're in keyboard modality.
126 | * @param {Event} e
127 | */
128 | function onPointerDown(e) {
129 | hadKeyboardEvent = false;
130 | }
131 |
132 | /**
133 | * On `focus`, add the `focus-visible` class to the target if:
134 | * - the target received focus as a result of keyboard navigation, or
135 | * - the event target is an element that will likely require interaction
136 | * via the keyboard (e.g. a text box)
137 | * @param {Event} e
138 | */
139 | function onFocus(e) {
140 | // Prevent IE from focusing the document or HTML element.
141 | if (!isValidFocusTarget(e.target)) {
142 | return;
143 | }
144 |
145 | if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {
146 | addFocusVisibleClass(e.target);
147 | }
148 | }
149 |
150 | /**
151 | * On `blur`, remove the `focus-visible` class from the target.
152 | * @param {Event} e
153 | */
154 | function onBlur(e) {
155 | if (!isValidFocusTarget(e.target)) {
156 | return;
157 | }
158 |
159 | if (e.target.classList.contains('focus-visible') || e.target.hasAttribute('data-focus-visible-added')) {
160 | // To detect a tab/window switch, we look for a blur event followed
161 | // rapidly by a visibility change.
162 | // If we don't see a visibility change within 100ms, it's probably a
163 | // regular focus change.
164 | hadFocusVisibleRecently = true;
165 | window.clearTimeout(hadFocusVisibleRecentlyTimeout);
166 | hadFocusVisibleRecentlyTimeout = window.setTimeout(function () {
167 | hadFocusVisibleRecently = false;
168 | }, 100);
169 | removeFocusVisibleClass(e.target);
170 | }
171 | }
172 |
173 | /**
174 | * If the user changes tabs, keep track of whether or not the previously
175 | * focused element had .focus-visible.
176 | * @param {Event} e
177 | */
178 | function onVisibilityChange(e) {
179 | if (document.visibilityState === 'hidden') {
180 | // If the tab becomes active again, the browser will handle calling focus
181 | // on the element (Safari actually calls it twice).
182 | // If this tab change caused a blur on an element with focus-visible,
183 | // re-apply the class when the user switches back to the tab.
184 | if (hadFocusVisibleRecently) {
185 | hadKeyboardEvent = true;
186 | }
187 | addInitialPointerMoveListeners();
188 | }
189 | }
190 |
191 | /**
192 | * Add a group of listeners to detect usage of any pointing devices.
193 | * These listeners will be added when the polyfill first loads, and anytime
194 | * the window is blurred, so that they are active when the window regains
195 | * focus.
196 | */
197 | function addInitialPointerMoveListeners() {
198 | document.addEventListener('mousemove', onInitialPointerMove);
199 | document.addEventListener('mousedown', onInitialPointerMove);
200 | document.addEventListener('mouseup', onInitialPointerMove);
201 | document.addEventListener('pointermove', onInitialPointerMove);
202 | document.addEventListener('pointerdown', onInitialPointerMove);
203 | document.addEventListener('pointerup', onInitialPointerMove);
204 | document.addEventListener('touchmove', onInitialPointerMove);
205 | document.addEventListener('touchstart', onInitialPointerMove);
206 | document.addEventListener('touchend', onInitialPointerMove);
207 | }
208 |
209 | function removeInitialPointerMoveListeners() {
210 | document.removeEventListener('mousemove', onInitialPointerMove);
211 | document.removeEventListener('mousedown', onInitialPointerMove);
212 | document.removeEventListener('mouseup', onInitialPointerMove);
213 | document.removeEventListener('pointermove', onInitialPointerMove);
214 | document.removeEventListener('pointerdown', onInitialPointerMove);
215 | document.removeEventListener('pointerup', onInitialPointerMove);
216 | document.removeEventListener('touchmove', onInitialPointerMove);
217 | document.removeEventListener('touchstart', onInitialPointerMove);
218 | document.removeEventListener('touchend', onInitialPointerMove);
219 | }
220 |
221 | /**
222 | * When the polfyill first loads, assume the user is in keyboard modality.
223 | * If any event is received from a pointing device (e.g. mouse, pointer,
224 | * touch), turn off keyboard modality.
225 | * This accounts for situations where focus enters the page from the URL bar.
226 | * @param {Event} e
227 | */
228 | function onInitialPointerMove(e) {
229 | // Work around a Safari quirk that fires a mousemove on whenever the
230 | // window blurs, even if you're tabbing out of the page. ¯\_(ツ)_/¯
231 | if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {
232 | return;
233 | }
234 |
235 | hadKeyboardEvent = false;
236 | removeInitialPointerMoveListeners();
237 | }
238 |
239 | // For some kinds of state, we are interested in changes at the global scope
240 | // only. For example, global pointer input, global key presses and global
241 | // visibility change should affect the state at every scope:
242 | document.addEventListener('keydown', onKeyDown, true);
243 | document.addEventListener('mousedown', onPointerDown, true);
244 | document.addEventListener('pointerdown', onPointerDown, true);
245 | document.addEventListener('touchstart', onPointerDown, true);
246 | document.addEventListener('visibilitychange', onVisibilityChange, true);
247 |
248 | addInitialPointerMoveListeners();
249 |
250 | // For focus and blur, we specifically care about state changes in the local
251 | // scope. This is because focus / blur events that originate from within a
252 | // shadow root are not re-dispatched from the host element if it was already
253 | // the active element in its own scope:
254 | scope.addEventListener('focus', onFocus, true);
255 | scope.addEventListener('blur', onBlur, true);
256 |
257 | // We detect that a node is a ShadowRoot by ensuring that it is a
258 | // DocumentFragment and also has a host property. This check covers native
259 | // implementation and polyfill implementation transparently. If we only cared
260 | // about the native implementation, we could just check if the scope was
261 | // an instance of a ShadowRoot.
262 | if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {
263 | // Since a ShadowRoot is a special kind of DocumentFragment, it does not
264 | // have a root element to add a class to. So, we add this attribute to the
265 | // host element instead:
266 | scope.host.setAttribute('data-js-focus-visible', '');
267 | } else if (scope.nodeType === Node.DOCUMENT_NODE) {
268 | document.documentElement.classList.add('js-focus-visible');
269 | document.documentElement.setAttribute('data-js-focus-visible', '');
270 | }
271 | }
272 |
273 | // It is important to wrap all references to global window and document in
274 | // these checks to support server-side rendering use cases
275 | // @see https://github.com/WICG/focus-visible/issues/199
276 | if (typeof window !== 'undefined' && typeof document !== 'undefined') {
277 | // Make the polyfill helper globally available. This can be used as a signal
278 | // to interested libraries that wish to coordinate with the polyfill for e.g.,
279 | // applying the polyfill to a shadow root:
280 | window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;
281 |
282 | // Notify interested libraries of the polyfill's presence, in case the
283 | // polyfill was loaded lazily:
284 | var event;
285 |
286 | try {
287 | event = new CustomEvent('focus-visible-polyfill-ready');
288 | } catch (error) {
289 | // IE11 does not support using CustomEvent as a constructor directly:
290 | event = document.createEvent('CustomEvent');
291 | event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});
292 | }
293 |
294 | window.dispatchEvent(event);
295 | }
296 |
297 | if (typeof document !== 'undefined') {
298 | // Apply the polyfill to the global document, so that no JavaScript
299 | // coordination is required to use the polyfill in the top-level document:
300 | applyFocusVisiblePolyfill(document);
301 | }
302 |
--------------------------------------------------------------------------------
/source/sass/blocks/btn.scss:
--------------------------------------------------------------------------------
1 | .btn {
2 | display: inline-flex;
3 | padding: 10px 20px;
4 |
5 | color: $color-default-white;
6 |
7 | background-color: $color-neon-blue;
8 | border: none;
9 | cursor: pointer;
10 |
11 | transition: opacity $trans-default;
12 |
13 | &--red {
14 | background-color: $color-torch-red;
15 | }
16 |
17 | &:disabled {
18 | opacity: 0.5;
19 |
20 | pointer-events: none;
21 | }
22 |
23 | @include hover-focus {
24 | opacity: 0.8;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/source/sass/blocks/footer.scss:
--------------------------------------------------------------------------------
1 | .footer {
2 | padding: 40px 0;
3 |
4 | background-color: $color-neon-blue;
5 | }
6 |
--------------------------------------------------------------------------------
/source/sass/blocks/header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | padding: 28px 0;
3 |
4 | background-color: $color-neon-blue;
5 |
6 | .container {
7 | display: flex;
8 | }
9 |
10 | &__logo {
11 | color: $color-default-white;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/source/sass/blocks/main-nav.scss:
--------------------------------------------------------------------------------
1 | .main-nav {
2 | &__list {
3 | display: flex;
4 | margin: 0;
5 | margin-right: 20px;
6 | margin-left: 60px;
7 | padding: 0;
8 |
9 | list-style: none;
10 | }
11 |
12 | &__item {
13 | margin-right: 20px;
14 | }
15 |
16 | &__link {
17 | color: $color-default-white;
18 |
19 | &.is-active {
20 | opacity: 0.6;
21 | }
22 | }
23 |
24 | &__toggle {
25 | display: none;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/source/sass/blocks/modal.scss:
--------------------------------------------------------------------------------
1 | .modal {
2 | position: fixed;
3 | top: 0;
4 | right: 0;
5 | bottom: 0;
6 | left: 0;
7 | z-index: 1000;
8 |
9 | display: flex;
10 | flex-direction: column;
11 | overflow-y: auto;
12 | -webkit-overflow-scrolling: touch;
13 |
14 | background-color: rgba($color-default-black, 0.8);
15 | transform: scale(1.2);
16 | visibility: hidden;
17 | opacity: 0;
18 |
19 | transition: opacity $trans-modal,
20 | transform $trans-modal,
21 | visibility $trans-modal;
22 |
23 | pointer-events: none;
24 |
25 | -ms-overflow-style: none;
26 | scrollbar-width: none;
27 |
28 | &::-webkit-scrollbar {
29 | display: none;
30 | }
31 |
32 | &__wrapper {
33 | position: relative;
34 |
35 | display: flex;
36 | flex-shrink: 0;
37 | justify-content: center;
38 | align-items: center;
39 | width: 100%;
40 | min-height: 100%;
41 | padding: 60px 40px;
42 |
43 | @include vp-767 {
44 | padding: 60px 16px;
45 | }
46 | }
47 |
48 | &__overlay {
49 | position: absolute;
50 | top: 0;
51 | left: 0;
52 |
53 | width: 100%;
54 | height: 100%;
55 |
56 | cursor: pointer;
57 | }
58 |
59 | &__content {
60 | position: relative;
61 |
62 | width: 600px;
63 | padding: 40px;
64 |
65 | background-color: $color-default-white;
66 |
67 | @include vp-767 {
68 | width: 100%;
69 | padding: 20px;
70 | }
71 | }
72 |
73 | &__close-btn {
74 | position: absolute;
75 | top: 22px;
76 | right: 22px;
77 | z-index: 1;
78 |
79 | width: 40px;
80 | height: 40px;
81 | padding: 0;
82 |
83 | background: $color-transparent;
84 | border: none;
85 | cursor: pointer;
86 | }
87 |
88 | &--responsive {
89 | .modal__content {
90 | width: 100%;
91 | }
92 | }
93 |
94 | &--fit-content {
95 | .modal__content {
96 | width: auto;
97 |
98 | @include vp-767 {
99 | width: 100%;
100 | }
101 | }
102 | }
103 |
104 | &--no-scale {
105 | transform: none;
106 | }
107 |
108 | &--preload {
109 | transition: none;
110 | }
111 |
112 | &.is-active {
113 | transform: scale(1);
114 | visibility: visible;
115 | opacity: 1;
116 |
117 | pointer-events: auto;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/source/sass/functions.scss:
--------------------------------------------------------------------------------
1 | // ---------------------------------
2 | // ❗ использование скейлинга должно быть согласовано с командой и заказчиком
3 |
4 | // 1) ремы в плюс
5 |
6 | // functions.scss
7 | // @function size($size-in-px) {
8 | // @return calc($size-in-px / $fz-default * 1rem);
9 | // }
10 |
11 | // @function size-desktop($size-in-px) {
12 | // @return calc($size-in-px / $vp-1920 * 100vw);
13 | // }
14 |
15 | // reboot.scss
16 | // html {
17 | // font-size: size-desktop($fz-default);
18 |
19 | // @include vp-1919 {
20 | // font-size: $fz-default;
21 | // }
22 | // }
23 |
24 | // ---------------------------------
25 | // 2) ремы в плюс от эталона и в минус, на планшете обычная верстка
26 |
27 | // functions.scss
28 | // @function size($size-in-px) {
29 | // @return calc($size-in-px / $fz-default * 1rem;
30 | // }
31 |
32 | // @function size-desktop($size-in-px) {
33 | // @return calc($size-in-px / $vp-1920 * 100vw;
34 | // }
35 |
36 | // reboot.scss
37 | // html {
38 | // font-size: size-desktop($fz-default);
39 |
40 | // @include vp-1023 {
41 | // font-size: $fz-default;
42 | // }
43 | // }
44 |
45 | // ---------------------------------
46 | // 3) ремы в плюс от эталона и в минус (с другим коэффициентом), на планшете обычная верстка
47 | // functions.scss
48 | // @function size($size-in-px) {
49 | // @return calc($size-in-px / $fz-default * 1rem);
50 | // }
51 |
52 | // @function size-desktop($size-in-px) {
53 | // @return calc($size-in-px / $vp-1920 * 100vw);
54 | // }
55 |
56 | // @function size-tablet($size-in-px) {
57 | // @return calc($size-in-px / $vp-1440 * 100vw);
58 | // }
59 |
60 | // reboot.scss
61 | // html {
62 | // font-size: size-desktop($fz-default);
63 |
64 | // @include vp-1439 {
65 | // font-size: size-tablet($fz-default);
66 | // }
67 |
68 | // @include vp-1023 {
69 | // font-size: $fz-default;
70 | // }
71 | // }
72 |
73 | // ---------------------------------
74 | // 4) ремы в коэффициентах
75 | // functions.scss
76 | // @function size($size-in-px) {
77 | // @return calc($size-in-px / $fz-default * 1rem);
78 | // }
79 |
80 | // @function size-coefficient($size-in-px) {
81 | // @return calc($size-in-px * 0.9);
82 | // }
83 |
84 | // @function size-coefficient-large($size-in-px) {
85 | // @return calc($size-in-px * 1.3);
86 | // }
87 |
88 | // reboot.scss
89 | // html {
90 | // font-size: $fz-default;
91 |
92 | // @media (min-width: 1921px) {
93 | // font-size: size-coefficient-large($fz-default);
94 | // }
95 |
96 | // @include vp-1439 {
97 | // font-size: size-coefficient($fz-default);
98 | // }
99 |
100 | // @include vp-1023 {
101 | // font-size: $fz-default;
102 | // }
103 | // }
104 |
105 | // ---------------------------------
106 | // 5) скейлинг отдельных элементов
107 | // functions.scss
108 | // @function size($size-in-px) {
109 | // @return calc($size-in-px / $fz-default * 1rem);
110 | // }
111 |
112 | // @function size-desktop($size-in-px) {
113 | // @return calc($size-in-px / $vp-1920 * 100vw);
114 | // }
115 |
116 | // @function no-scale($size-in-px) {
117 | // @return calc($size-in-px * (1921px / 1920px));
118 | // }
119 |
120 | // reboot.scss
121 | // html {
122 | // font-size: size-desktop($fz-default);
123 |
124 | // @include vp-1439 {
125 | // font-size: $fz-default;
126 | // }
127 | // }
128 |
129 | // .block {
130 | // margin-bottom: size(40px);
131 |
132 | // @media (min-width: 1921px) {
133 | // margin-bottom: no-scale(40px);
134 | // }
135 | // }
136 |
--------------------------------------------------------------------------------
/source/sass/global/container.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | max-width: 1920px;
4 | margin: 0 auto;
5 | padding: 0 40px;
6 |
7 | @include vp-1439 {
8 | padding: 0 32px;
9 | }
10 |
11 | @include vp-1023 {
12 | padding: 0 24px;
13 | }
14 |
15 | @include vp-767 {
16 | padding: 0 16px;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/source/sass/global/fonts.scss:
--------------------------------------------------------------------------------
1 | // @font-face {
2 | // font-style: normal;
3 | // font-weight: 400;
4 | // font-family: "Rouble";
5 |
6 | // font-display: swap;
7 | // src:
8 | // url("../fonts/rouble.woff2") format("woff2"),
9 | // url("../fonts/rouble.woff") format("woff");
10 | // }
11 |
12 | @font-face {
13 | font-style: normal;
14 | font-weight: 400;
15 | font-family: "";
16 |
17 | font-display: swap;
18 | src:
19 | url("../fonts/.woff2") format("woff2"),
20 | url("../fonts/.woff") format("woff");
21 | }
22 |
--------------------------------------------------------------------------------
/source/sass/global/reboot.scss:
--------------------------------------------------------------------------------
1 | *,
2 | *::before,
3 | *::after {
4 | box-sizing: border-box;
5 | }
6 |
7 | html,
8 | body {
9 | margin: 0;
10 | padding: 0;
11 | min-height: 100vh;
12 | }
13 |
14 | html {
15 | font-style: normal;
16 | font-weight: 400;
17 | font-size: $fz-default;
18 | line-height: 24px;
19 | font-family: $ff-placeholder;
20 | color: $color-default-black;
21 |
22 | -webkit-font-smoothing: antialiased;
23 | -moz-osx-font-smoothing: grayscale;
24 | // оптимизация выравнивания шрифта относительно центра строки
25 | text-rendering: optimizeLegibility;
26 | // если по прежнему есть проблемы с выравниванием
27 | // https://transfonter.org/ - включите настройку https://prnt.sc/12rnt6g и переконвертируйте шрифт
28 | }
29 |
30 | body {
31 | width: 100%;
32 | height: 100%;
33 |
34 | background-color: $color-default-white;
35 | }
36 |
37 | a {
38 | color: $color-default-black;
39 | text-decoration: none;
40 | }
41 |
42 | img,
43 | video {
44 | display: block;
45 | max-width: 100%;
46 | height: auto;
47 | }
48 |
49 | textarea {
50 | resize: none;
51 | }
52 |
53 | // chrome autofill background removal
54 | // если на проекте у инпутов используются разные цвета фонов\текста -
55 | // удалите это из глобала и используйте локально с нужными цветами
56 | // rgba не подойдет, сконвертируйте цвет в hex без прозрачности
57 | // если в стилях уже используется box-shadow есть другое решение -
58 | // задать к списку транзишенов `background-color 10000000s ease-out`
59 | input:-webkit-autofill {
60 | box-shadow: inset 0 0 0 1000px $color-default-white;
61 |
62 | -webkit-text-fill-color: $color-default-black;
63 | }
64 |
65 | // firefox placeholder \ invalid fix + ios bdrs
66 | input,
67 | textarea {
68 | border-radius: 0;
69 |
70 | &::placeholder {
71 | opacity: 1;
72 | }
73 |
74 | &:invalid {
75 | box-shadow: none;
76 | }
77 | }
78 |
79 | select {
80 | border-radius: 0;
81 | }
82 |
83 | // ie11 X removal
84 | input {
85 | &::-ms-clear,
86 | &::-ms-reveal {
87 | display: none;
88 | }
89 | }
90 |
91 | // chrome search X removal
92 | input[type="search"]::-webkit-search-decoration,
93 | input[type="search"]::-webkit-search-cancel-button,
94 | input[type="search"]::-webkit-search-results-button,
95 | input[type="search"]::-webkit-search-results-decoration {
96 | appearance: none;
97 | }
98 |
99 | // input[number] arrows removal
100 | input::-webkit-outer-spin-button,
101 | input::-webkit-inner-spin-button {
102 | margin: 0;
103 |
104 | appearance: none;
105 | }
106 |
107 | input[type="number"] {
108 | appearance: textfield;
109 | }
110 |
111 | // ios button \ inputs reset
112 | select,
113 | textarea,
114 | input:matches([type="email"],
115 | [type="number"],
116 | [type="password"],
117 | [type="search"],
118 | [type="tel"],
119 | [type="text"],
120 | [type="url"]) {
121 | appearance: none;
122 | }
123 |
124 | button,
125 | [type="button"],
126 | [type="reset"],
127 | [type="submit"] {
128 | appearance: none;
129 | }
130 |
--------------------------------------------------------------------------------
/source/sass/global/utils.scss:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | position: relative;
3 |
4 | display: flex;
5 | flex-direction: column;
6 | min-height: 100vh;
7 | /* stylelint-disable-next-line */
8 | min-height: calc(100 * var(--vh, 1vh));
9 |
10 | .header,
11 | .footer {
12 | flex-shrink: 0;
13 | }
14 |
15 | main {
16 | flex-grow: 1;
17 | }
18 | }
19 |
20 | .rouble {
21 | font-family: $ff-rouble;
22 | /* stylelint-disable */
23 | text-transform: lowercase !important;
24 | font-weight: 400 !important;
25 | /* stylelint-enable */
26 | }
27 |
28 | .visually-hidden {
29 | position: absolute;
30 |
31 | width: 1px;
32 | height: 1px;
33 | margin: -1px;
34 | padding: 0;
35 | overflow: hidden;
36 |
37 | white-space: nowrap;
38 |
39 | border: 0;
40 |
41 | clip: rect(0 0 0 0);
42 | clip-path: inset(100%);
43 | }
44 |
45 | .scroll-lock-ios {
46 | position: fixed;
47 |
48 | overflow: hidden;
49 | }
50 |
51 | .scroll-lock {
52 | overflow: hidden;
53 | }
54 |
55 | .no-scrollbar {
56 | -ms-overflow-style: none;
57 | scrollbar-width: none;
58 |
59 | &::-webkit-scrollbar {
60 | display: none;
61 | }
62 | }
63 |
64 | .no-transition {
65 | /* stylelint-disable-next-line */
66 | transition: none !important;
67 | }
68 |
--------------------------------------------------------------------------------
/source/sass/keyframes.scss:
--------------------------------------------------------------------------------
1 | // используйте для скрытия нежелательных анимаций-скачков при поворотах экрана
2 | @keyframes disableBreakpointAnimation {
3 | 0% {
4 | visibility: hidden;
5 | opacity: 0;
6 | }
7 |
8 | 100% {
9 | visibility: hidden;
10 | opacity: 0;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/source/sass/mixins.scss:
--------------------------------------------------------------------------------
1 | @mixin retina {
2 | @media (min-resolution: $retina-dpi), (min-resolution: $retina-dppx) {
3 | @content;
4 | }
5 | }
6 |
7 | @mixin hover {
8 | @media (hover: hover) {
9 | &:hover:not(.focus-visible) {
10 | @content;
11 | }
12 | }
13 | }
14 |
15 | @mixin focus {
16 | &.focus-visible:focus {
17 | @content;
18 | }
19 | }
20 |
21 | @mixin active {
22 | &.focus-visible:active {
23 | @content;
24 | }
25 | }
26 |
27 | @mixin hover-focus {
28 | @media (hover: hover) {
29 | &:hover:not(.focus-visible) {
30 | @content;
31 | }
32 | }
33 |
34 | &.focus-visible:focus {
35 | @content;
36 | }
37 | }
38 |
39 | // Desktop first
40 |
41 | @mixin vp-1919 {
42 | @media (max-width: ($vp-1920 - 1px)) {
43 | @content;
44 | }
45 | }
46 |
47 | @mixin vp-1439 {
48 | @media (max-width: ($vp-1440 - 1px)) {
49 | @content;
50 | }
51 | }
52 |
53 | @mixin vp-1279 {
54 | @media (max-width: ($vp-1280 - 1px)) {
55 | @content;
56 | }
57 | }
58 |
59 | @mixin vp-1023 {
60 | @media (max-width: ($vp-1024 - 1px)) {
61 | @content;
62 | }
63 | }
64 |
65 | @mixin vp-767 {
66 | @media (max-width: ($vp-768 - 1px)) {
67 | @content;
68 | }
69 | }
70 |
71 | @mixin vp-374 {
72 | @media (max-width: ($vp-375 - 1px)) {
73 | @content;
74 | }
75 | }
76 |
77 | // Mobile first
78 |
79 | // @mixin vp-375 {
80 | // @media (min-width: $vp-375) {
81 | // @content;
82 | // }
83 | // }
84 |
85 | // @mixin vp-768 {
86 | // @media (min-width: $vp-768) {
87 | // @content;
88 | // }
89 | // }
90 |
91 | // @mixin vp-1024 {
92 | // @media (min-width: $vp-1024) {
93 | // @content;
94 | // }
95 | // }
96 |
97 | // @mixin vp-1280 {
98 | // @media (min-width: $vp-1280) {
99 | // @content;
100 | // }
101 | // }
102 |
103 | // @mixin vp-1440 {
104 | // @media (min-width: $vp-1440) {
105 | // @content;
106 | // }
107 | // }
108 |
109 | // @mixin vp-1920 {
110 | // @media (min-width: $vp-1920) {
111 | // @content;
112 | // }
113 | // }
114 |
115 | // ---------------------------------
116 |
117 | // не нужно добавлять свои миксины для ресета списков, обнуления кнопок и прочие
118 | // этим вы усложняете работу команды, проверку вашего кода и будущую поддержку проекта
119 |
--------------------------------------------------------------------------------
/source/sass/style.scss:
--------------------------------------------------------------------------------
1 | // Vendor
2 | // ---------------------------------
3 |
4 | @import "vendor/normalize";
5 | // Swiper 7.4.1
6 | // @import "vendor/swiper";
7 |
8 | // Global
9 | // ---------------------------------
10 |
11 | @import "variables";
12 | @import "mixins";
13 | @import "functions";
14 | @import "global/fonts";
15 | @import "global/reboot";
16 | @import "global/utils";
17 | @import "global/container";
18 |
19 | // Blocks
20 | // ---------------------------------
21 |
22 | @import "blocks/header";
23 | @import "blocks/main-nav";
24 | @import "blocks/footer";
25 | @import "blocks/modal";
26 | @import "blocks/btn";
27 |
28 | // ---------------------------------
29 |
30 | // ❗❗❗ запрещено использовать изображения в css, касается как jpg \ png, так и svg
31 |
32 | // ❗ обязательно используйте mixin hover-focus для отключения ховеров на тач устройствах
33 | /* @mixin hover-focus {
34 | @media (hover: hover) {
35 | &:hover:not(.focus-visible) {
36 | @content;
37 | }
38 | }
39 |
40 | &.focus-visible:focus {
41 | @content;
42 | }
43 | }
44 | */
45 | /* @include hover-focus {
46 | opacity: 0.8;
47 | }
48 | */
49 | // но не используем для текстовых полей ( input, textarea )
50 | // так же в сборке есть отдельный миксин для hover
51 | /*@mixin hover {
52 | @media (hover: hover) {
53 | &:hover:not(.focus-visible) {
54 | @content;
55 | }
56 | }
57 | }
58 | */
59 | // для focus
60 | /*@mixin focus {
61 | &.focus-visible:focus {
62 | @content;
63 | }
64 | }
65 | */
66 | // и для active
67 | /*@mixin active {
68 | &.focus-visible:active {
69 | @content;
70 | }
71 | }
72 | */
73 | // адаптив пишем внутри каждого класса (смотрим container.scss)
74 |
75 | // для фикса проблем с vh на iOS в сборке подключен скрипт
76 | // используя vh на проекте задавайте их также как в примере в utils.scss
77 |
78 | // для любых transition обязательно указывайте transition-property
79 | // transition: $trans-default ❌ ---> transition: color $trans-default ✅
80 |
--------------------------------------------------------------------------------
/source/sass/variables.scss:
--------------------------------------------------------------------------------
1 | // Colors
2 | // ---------------------------------
3 |
4 | // Default
5 |
6 | $color-default-black: #000000;
7 | $color-default-white: #ffffff;
8 | $color-transparent: rgba(255, 255, 255, 0);
9 |
10 | // Project palette
11 | // для нейминга цветов используем https://www.htmlcsscolor.com/hex/334482
12 |
13 | $color-neon-blue: #2c39f2;
14 | $color-torch-red: #ff1553;
15 |
16 | // Gradient
17 |
18 | $black-to-right: linear-gradient(90deg, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0) 75%);
19 |
20 | // Typography
21 | // ---------------------------------
22 |
23 | // переменная используемая в html для подключения скейлинга
24 | $fz-default: 16px;
25 |
26 | // у некоторых шрифтов в ios возникают проблемы с символом рубля
27 | // https://www.artlebedev.ru/kovodstvo/sections/159/#13
28 | $ff-rouble: "Rouble", "Arial", sans-serif;
29 |
30 | $ff-placeholder: "Placeholder", "Arial", sans-serif;
31 |
32 | // Animation
33 | // ---------------------------------
34 |
35 | $tf-default: ease;
36 | $trans-default: 0.3s $tf-default;
37 |
38 | // если требуется изинг отличный от $tf-default, то переменную называем не цифрой
39 | $trans-modal: 0.6s cubic-bezier(0.55, 0, 0.1, 1);
40 |
41 | $trans-600: 0.6s $tf-default;
42 |
43 | // Viewports
44 | // ---------------------------------
45 |
46 | $vp-320: 320px;
47 | $vp-375: 375px;
48 | $vp-768: 768px;
49 | $vp-1024: 1024px;
50 | $vp-1280: 1280px;
51 | $vp-1440: 1440px;
52 | $vp-1920: 1920px;
53 |
54 | // Retina
55 | // ---------------------------------
56 |
57 | $retina-dpi: 144dpi;
58 | $retina-dppx: 1.5dppx;
59 |
--------------------------------------------------------------------------------
/source/sass/vendor/normalize.scss:
--------------------------------------------------------------------------------
1 | /* stylelint-disable */
2 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
3 |
4 | /* Document
5 | ========================================================================== */
6 |
7 | /**
8 | * 1. Correct the line height in all browsers.
9 | * 2. Prevent adjustments of font size after orientation changes in iOS.
10 | */
11 |
12 | html {
13 | line-height: 1.15; /* 1 */
14 |
15 | -webkit-text-size-adjust: 100%; /* 2 */
16 | }
17 |
18 | /* Sections
19 | ========================================================================== */
20 |
21 | /**
22 | * Remove the margin in all browsers.
23 | */
24 |
25 | body {
26 | margin: 0;
27 | }
28 |
29 | /**
30 | * Render the `main` element consistently in IE.
31 | */
32 |
33 | main {
34 | display: block;
35 | }
36 |
37 | /**
38 | * Correct the font size and margin on `h1` elements within `section` and
39 | * `article` contexts in Chrome, Firefox, and Safari.
40 | */
41 |
42 | h1 {
43 | margin: 0.67em 0;
44 |
45 | font-size: 2em;
46 | }
47 |
48 | /* Grouping content
49 | ========================================================================== */
50 |
51 | /**
52 | * 1. Add the correct box sizing in Firefox.
53 | * 2. Show the overflow in Edge and IE.
54 | */
55 |
56 | hr {
57 | box-sizing: content-box; /* 1 */
58 | height: 0; /* 1 */
59 | overflow: visible; /* 2 */
60 | }
61 |
62 | /**
63 | * 1. Correct the inheritance and scaling of font size in all browsers.
64 | * 2. Correct the odd `em` font sizing in all browsers.
65 | */
66 |
67 | pre {
68 | font-size: 1em; /* 2 */
69 | font-family: monospace, monospace; /* 1 */
70 | }
71 |
72 | /* Text-level semantics
73 | ========================================================================== */
74 |
75 | /**
76 | * Remove the gray background on active links in IE 10.
77 | */
78 |
79 | a {
80 | background-color: transparent;
81 | }
82 |
83 | /**
84 | * 1. Remove the bottom border in Chrome 57-
85 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
86 | */
87 |
88 | abbr[title] {
89 | text-decoration: underline; /* 2 */
90 | text-decoration: underline dotted; /* 2 */
91 |
92 | border-bottom: none; /* 1 */
93 | }
94 |
95 | /**
96 | * Add the correct font weight in Chrome, Edge, and Safari.
97 | */
98 |
99 | b,
100 | strong {
101 | font-weight: bolder;
102 | }
103 |
104 | /**
105 | * 1. Correct the inheritance and scaling of font size in all browsers.
106 | * 2. Correct the odd `em` font sizing in all browsers.
107 | */
108 |
109 | code,
110 | kbd,
111 | samp {
112 | font-size: 1em; /* 2 */
113 | font-family: monospace, monospace; /* 1 */
114 | }
115 |
116 | /**
117 | * Add the correct font size in all browsers.
118 | */
119 |
120 | small {
121 | font-size: 80%;
122 | }
123 |
124 | /**
125 | * Prevent `sub` and `sup` elements from affecting the line height in
126 | * all browsers.
127 | */
128 |
129 | sub,
130 | sup {
131 | position: relative;
132 |
133 | font-size: 75%;
134 | line-height: 0;
135 | vertical-align: baseline;
136 | }
137 |
138 | sub {
139 | bottom: -0.25em;
140 | }
141 |
142 | sup {
143 | top: -0.5em;
144 | }
145 |
146 | /* Embedded content
147 | ========================================================================== */
148 |
149 | /**
150 | * Remove the border on images inside links in IE 10.
151 | */
152 |
153 | img {
154 | border-style: none;
155 | }
156 |
157 | /* Forms
158 | ========================================================================== */
159 |
160 | /**
161 | * 1. Change the font styles in all browsers.
162 | * 2. Remove the margin in Firefox and Safari.
163 | */
164 |
165 | button,
166 | input,
167 | optgroup,
168 | select,
169 | textarea {
170 | margin: 0; /* 2 */
171 |
172 | font-size: 100%; /* 1 */
173 | line-height: 1.15; /* 1 */
174 | font-family: inherit; /* 1 */
175 | }
176 |
177 | /**
178 | * Show the overflow in IE.
179 | * 1. Show the overflow in Edge.
180 | */
181 |
182 | button,
183 | input {
184 | /* 1 */
185 | overflow: visible;
186 | }
187 |
188 | /**
189 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
190 | * 1. Remove the inheritance of text transform in Firefox.
191 | */
192 |
193 | button,
194 | select {
195 | /* 1 */
196 | text-transform: none;
197 | }
198 |
199 | /**
200 | * Correct the inability to style clickable types in iOS and Safari.
201 | */
202 |
203 | button,
204 | [type="button"],
205 | [type="reset"],
206 | [type="submit"] {
207 | -webkit-appearance: button;
208 | }
209 |
210 | /**
211 | * Remove the inner border and padding in Firefox.
212 | */
213 |
214 | button::-moz-focus-inner,
215 | [type="button"]::-moz-focus-inner,
216 | [type="reset"]::-moz-focus-inner,
217 | [type="submit"]::-moz-focus-inner {
218 | padding: 0;
219 |
220 | border-style: none;
221 | }
222 |
223 | /**
224 | * Restore the focus styles unset by the previous rule.
225 | */
226 |
227 | button:-moz-focusring,
228 | [type="button"]:-moz-focusring,
229 | [type="reset"]:-moz-focusring,
230 | [type="submit"]:-moz-focusring {
231 | outline: 1px dotted ButtonText;
232 | }
233 |
234 | /**
235 | * Correct the padding in Firefox.
236 | */
237 |
238 | fieldset {
239 | padding: 0.35em 0.75em 0.625em;
240 | }
241 |
242 | /**
243 | * 1. Correct the text wrapping in Edge and IE.
244 | * 2. Correct the color inheritance from `fieldset` elements in IE.
245 | * 3. Remove the padding so developers are not caught out when they zero out
246 | * `fieldset` elements in all browsers.
247 | */
248 |
249 | legend {
250 | display: table; /* 1 */
251 | box-sizing: border-box; /* 1 */
252 | max-width: 100%; /* 1 */
253 | padding: 0; /* 3 */
254 |
255 | color: inherit; /* 2 */
256 | white-space: normal; /* 1 */
257 | }
258 |
259 | /**
260 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
261 | */
262 |
263 | progress {
264 | vertical-align: baseline;
265 | }
266 |
267 | /**
268 | * Remove the default vertical scrollbar in IE 10+.
269 | */
270 |
271 | textarea {
272 | overflow: auto;
273 | }
274 |
275 | /**
276 | * 1. Add the correct box sizing in IE 10.
277 | * 2. Remove the padding in IE 10.
278 | */
279 |
280 | [type="checkbox"],
281 | [type="radio"] {
282 | box-sizing: border-box; /* 1 */
283 | padding: 0; /* 2 */
284 | }
285 |
286 | /**
287 | * Correct the cursor style of increment and decrement buttons in Chrome.
288 | */
289 |
290 | [type="number"]::-webkit-inner-spin-button,
291 | [type="number"]::-webkit-outer-spin-button {
292 | height: auto;
293 | }
294 |
295 | /**
296 | * 1. Correct the odd appearance in Chrome and Safari.
297 | * 2. Correct the outline style in Safari.
298 | */
299 |
300 | [type="search"] {
301 | outline-offset: -2px; /* 2 */
302 |
303 | -webkit-appearance: textfield; /* 1 */
304 | }
305 |
306 | /**
307 | * Remove the inner padding in Chrome and Safari on macOS.
308 | */
309 |
310 | [type="search"]::-webkit-search-decoration {
311 | -webkit-appearance: none;
312 | }
313 |
314 | /**
315 | * 1. Correct the inability to style clickable types in iOS and Safari.
316 | * 2. Change font properties to `inherit` in Safari.
317 | */
318 |
319 | ::-webkit-file-upload-button {
320 | font: inherit; /* 2 */
321 |
322 | -webkit-appearance: button; /* 1 */
323 | }
324 |
325 | /* Interactive
326 | ========================================================================== */
327 |
328 | /*
329 | * Add the correct display in Edge, IE 10+, and Firefox.
330 | */
331 |
332 | details {
333 | display: block;
334 | }
335 |
336 | /*
337 | * Add the correct display in all browsers.
338 | */
339 |
340 | summary {
341 | display: list-item;
342 | }
343 |
344 | /* Misc
345 | ========================================================================== */
346 |
347 | /**
348 | * Add the correct display in IE 10+.
349 | */
350 |
351 | template {
352 | display: none;
353 | }
354 |
355 | /**
356 | * Add the correct display in IE 10.
357 | */
358 |
359 | [hidden] {
360 | display: none;
361 | }
362 |
--------------------------------------------------------------------------------
/source/sass/vendor/swiper.scss:
--------------------------------------------------------------------------------
1 | /* stylelint-disable */
2 | /**
3 | * Swiper 7.4.1
4 | * Most modern mobile touch slider and framework with hardware accelerated transitions
5 | * https://swiperjs.com
6 | *
7 | * Copyright 2014-2021 Vladimir Kharlampidi
8 | *
9 | * Released under the MIT License
10 | *
11 | * Released on: December 24, 2021
12 | */
13 |
14 | @font-face {
15 | font-family: 'swiper-icons';
16 | src: url('data:application/font-woff;charset=utf-8;base64, d09GRgABAAAAAAZgABAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAGRAAAABoAAAAci6qHkUdERUYAAAWgAAAAIwAAACQAYABXR1BPUwAABhQAAAAuAAAANuAY7+xHU1VCAAAFxAAAAFAAAABm2fPczU9TLzIAAAHcAAAASgAAAGBP9V5RY21hcAAAAkQAAACIAAABYt6F0cBjdnQgAAACzAAAAAQAAAAEABEBRGdhc3AAAAWYAAAACAAAAAj//wADZ2x5ZgAAAywAAADMAAAD2MHtryVoZWFkAAABbAAAADAAAAA2E2+eoWhoZWEAAAGcAAAAHwAAACQC9gDzaG10eAAAAigAAAAZAAAArgJkABFsb2NhAAAC0AAAAFoAAABaFQAUGG1heHAAAAG8AAAAHwAAACAAcABAbmFtZQAAA/gAAAE5AAACXvFdBwlwb3N0AAAFNAAAAGIAAACE5s74hXjaY2BkYGAAYpf5Hu/j+W2+MnAzMYDAzaX6QjD6/4//Bxj5GA8AuRwMYGkAPywL13jaY2BkYGA88P8Agx4j+/8fQDYfA1AEBWgDAIB2BOoAeNpjYGRgYNBh4GdgYgABEMnIABJzYNADCQAACWgAsQB42mNgYfzCOIGBlYGB0YcxjYGBwR1Kf2WQZGhhYGBiYGVmgAFGBiQQkOaawtDAoMBQxXjg/wEGPcYDDA4wNUA2CCgwsAAAO4EL6gAAeNpj2M0gyAACqxgGNWBkZ2D4/wMA+xkDdgAAAHjaY2BgYGaAYBkGRgYQiAHyGMF8FgYHIM3DwMHABGQrMOgyWDLEM1T9/w8UBfEMgLzE////P/5//f/V/xv+r4eaAAeMbAxwIUYmIMHEgKYAYjUcsDAwsLKxc3BycfPw8jEQA/gZBASFhEVExcQlJKWkZWTl5BUUlZRVVNXUNTQZBgMAAMR+E+gAEQFEAAAAKgAqACoANAA+AEgAUgBcAGYAcAB6AIQAjgCYAKIArAC2AMAAygDUAN4A6ADyAPwBBgEQARoBJAEuATgBQgFMAVYBYAFqAXQBfgGIAZIBnAGmAbIBzgHsAAB42u2NMQ6CUAyGW568x9AneYYgm4MJbhKFaExIOAVX8ApewSt4Bic4AfeAid3VOBixDxfPYEza5O+Xfi04YADggiUIULCuEJK8VhO4bSvpdnktHI5QCYtdi2sl8ZnXaHlqUrNKzdKcT8cjlq+rwZSvIVczNiezsfnP/uznmfPFBNODM2K7MTQ45YEAZqGP81AmGGcF3iPqOop0r1SPTaTbVkfUe4HXj97wYE+yNwWYxwWu4v1ugWHgo3S1XdZEVqWM7ET0cfnLGxWfkgR42o2PvWrDMBSFj/IHLaF0zKjRgdiVMwScNRAoWUoH78Y2icB/yIY09An6AH2Bdu/UB+yxopYshQiEvnvu0dURgDt8QeC8PDw7Fpji3fEA4z/PEJ6YOB5hKh4dj3EvXhxPqH/SKUY3rJ7srZ4FZnh1PMAtPhwP6fl2PMJMPDgeQ4rY8YT6Gzao0eAEA409DuggmTnFnOcSCiEiLMgxCiTI6Cq5DZUd3Qmp10vO0LaLTd2cjN4fOumlc7lUYbSQcZFkutRG7g6JKZKy0RmdLY680CDnEJ+UMkpFFe1RN7nxdVpXrC4aTtnaurOnYercZg2YVmLN/d/gczfEimrE/fs/bOuq29Zmn8tloORaXgZgGa78yO9/cnXm2BpaGvq25Dv9S4E9+5SIc9PqupJKhYFSSl47+Qcr1mYNAAAAeNptw0cKwkAAAMDZJA8Q7OUJvkLsPfZ6zFVERPy8qHh2YER+3i/BP83vIBLLySsoKimrqKqpa2hp6+jq6RsYGhmbmJqZSy0sraxtbO3sHRydnEMU4uR6yx7JJXveP7WrDycAAAAAAAH//wACeNpjYGRgYOABYhkgZgJCZgZNBkYGLQZtIJsFLMYAAAw3ALgAeNolizEKgDAQBCchRbC2sFER0YD6qVQiBCv/H9ezGI6Z5XBAw8CBK/m5iQQVauVbXLnOrMZv2oLdKFa8Pjuru2hJzGabmOSLzNMzvutpB3N42mNgZGBg4GKQYzBhYMxJLMlj4GBgAYow/P/PAJJhLM6sSoWKfWCAAwDAjgbRAAB42mNgYGBkAIIbCZo5IPrmUn0hGA0AO8EFTQAA');
17 | font-weight: 400;
18 | font-style: normal;
19 | }
20 | :root {
21 | --swiper-theme-color: #007aff;
22 | }
23 | .swiper {
24 | margin-left: auto;
25 | margin-right: auto;
26 | position: relative;
27 | overflow: hidden;
28 | list-style: none;
29 | padding: 0;
30 | /* Fix of Webkit flickering */
31 | z-index: 1;
32 | }
33 | .swiper-vertical > .swiper-wrapper {
34 | flex-direction: column;
35 | }
36 | .swiper-wrapper {
37 | position: relative;
38 | width: 100%;
39 | height: 100%;
40 | z-index: 1;
41 | display: flex;
42 | transition-property: transform;
43 | box-sizing: content-box;
44 | }
45 | .swiper-android .swiper-slide,
46 | .swiper-wrapper {
47 | transform: translate3d(0px, 0, 0);
48 | }
49 | .swiper-pointer-events {
50 | touch-action: pan-y;
51 | }
52 | .swiper-pointer-events.swiper-vertical {
53 | touch-action: pan-x;
54 | }
55 | .swiper-slide {
56 | flex-shrink: 0;
57 | width: 100%;
58 | height: 100%;
59 | position: relative;
60 | transition-property: transform;
61 | }
62 | .swiper-slide-invisible-blank {
63 | visibility: hidden;
64 | }
65 | /* Auto Height */
66 | .swiper-autoheight,
67 | .swiper-autoheight .swiper-slide {
68 | height: auto;
69 | }
70 | .swiper-autoheight .swiper-wrapper {
71 | align-items: flex-start;
72 | transition-property: transform, height;
73 | }
74 | /* 3D Effects */
75 | .swiper-3d,
76 | .swiper-3d.swiper-css-mode .swiper-wrapper {
77 | perspective: 1200px;
78 | }
79 | .swiper-3d .swiper-wrapper,
80 | .swiper-3d .swiper-slide,
81 | .swiper-3d .swiper-slide-shadow,
82 | .swiper-3d .swiper-slide-shadow-left,
83 | .swiper-3d .swiper-slide-shadow-right,
84 | .swiper-3d .swiper-slide-shadow-top,
85 | .swiper-3d .swiper-slide-shadow-bottom,
86 | .swiper-3d .swiper-cube-shadow {
87 | transform-style: preserve-3d;
88 | }
89 | .swiper-3d .swiper-slide-shadow,
90 | .swiper-3d .swiper-slide-shadow-left,
91 | .swiper-3d .swiper-slide-shadow-right,
92 | .swiper-3d .swiper-slide-shadow-top,
93 | .swiper-3d .swiper-slide-shadow-bottom {
94 | position: absolute;
95 | left: 0;
96 | top: 0;
97 | width: 100%;
98 | height: 100%;
99 | pointer-events: none;
100 | z-index: 10;
101 | }
102 | .swiper-3d .swiper-slide-shadow {
103 | background: rgba(0, 0, 0, 0.15);
104 | }
105 | .swiper-3d .swiper-slide-shadow-left {
106 | background-image: linear-gradient(to left, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
107 | }
108 | .swiper-3d .swiper-slide-shadow-right {
109 | background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
110 | }
111 | .swiper-3d .swiper-slide-shadow-top {
112 | background-image: linear-gradient(to top, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
113 | }
114 | .swiper-3d .swiper-slide-shadow-bottom {
115 | background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
116 | }
117 | /* CSS Mode */
118 | .swiper-css-mode > .swiper-wrapper {
119 | overflow: auto;
120 | scrollbar-width: none;
121 | /* For Firefox */
122 | -ms-overflow-style: none;
123 | /* For Internet Explorer and Edge */
124 | }
125 | .swiper-css-mode > .swiper-wrapper::-webkit-scrollbar {
126 | display: none;
127 | }
128 | .swiper-css-mode > .swiper-wrapper > .swiper-slide {
129 | scroll-snap-align: start start;
130 | }
131 | .swiper-horizontal.swiper-css-mode > .swiper-wrapper {
132 | scroll-snap-type: x mandatory;
133 | }
134 | .swiper-vertical.swiper-css-mode > .swiper-wrapper {
135 | scroll-snap-type: y mandatory;
136 | }
137 | .swiper-centered > .swiper-wrapper::before {
138 | content: '';
139 | flex-shrink: 0;
140 | order: 9999;
141 | }
142 | .swiper-centered.swiper-horizontal > .swiper-wrapper > .swiper-slide:first-child {
143 | margin-inline-start: var(--swiper-centered-offset-before);
144 | }
145 | .swiper-centered.swiper-horizontal > .swiper-wrapper::before {
146 | height: 100%;
147 | min-height: 1px;
148 | width: var(--swiper-centered-offset-after);
149 | }
150 | .swiper-centered.swiper-vertical > .swiper-wrapper > .swiper-slide:first-child {
151 | margin-block-start: var(--swiper-centered-offset-before);
152 | }
153 | .swiper-centered.swiper-vertical > .swiper-wrapper::before {
154 | width: 100%;
155 | min-width: 1px;
156 | height: var(--swiper-centered-offset-after);
157 | }
158 | .swiper-centered > .swiper-wrapper > .swiper-slide {
159 | scroll-snap-align: center center;
160 | }
161 | .swiper-virtual.swiper-css-mode .swiper-wrapper::after {
162 | content: '';
163 | position: absolute;
164 | left: 0;
165 | top: 0;
166 | pointer-events: none;
167 | }
168 | .swiper-virtual.swiper-css-mode.swiper-horizontal .swiper-wrapper::after {
169 | height: 1px;
170 | width: var(--swiper-virtual-size);
171 | }
172 | .swiper-virtual.swiper-css-mode.swiper-vertical .swiper-wrapper::after {
173 | width: 1px;
174 | height: var(--swiper-virtual-size);
175 | }
176 | :root {
177 | --swiper-navigation-size: 44px;
178 | /*
179 | --swiper-navigation-color: var(--swiper-theme-color);
180 | */
181 | }
182 | .swiper-button-prev,
183 | .swiper-button-next {
184 | position: absolute;
185 | top: 50%;
186 | width: calc(var(--swiper-navigation-size) / 44 * 27);
187 | height: var(--swiper-navigation-size);
188 | margin-top: calc(0px - (var(--swiper-navigation-size) / 2));
189 | z-index: 10;
190 | cursor: pointer;
191 | display: flex;
192 | align-items: center;
193 | justify-content: center;
194 | color: var(--swiper-navigation-color, var(--swiper-theme-color));
195 | }
196 | .swiper-button-prev.swiper-button-disabled,
197 | .swiper-button-next.swiper-button-disabled {
198 | opacity: 0.35;
199 | cursor: auto;
200 | pointer-events: none;
201 | }
202 | .swiper-button-prev:after,
203 | .swiper-button-next:after {
204 | font-family: swiper-icons;
205 | font-size: var(--swiper-navigation-size);
206 | text-transform: none !important;
207 | letter-spacing: 0;
208 | text-transform: none;
209 | font-variant: initial;
210 | line-height: 1;
211 | }
212 | .swiper-button-prev,
213 | .swiper-rtl .swiper-button-next {
214 | left: 10px;
215 | right: auto;
216 | }
217 | .swiper-button-prev:after,
218 | .swiper-rtl .swiper-button-next:after {
219 | content: 'prev';
220 | }
221 | .swiper-button-next,
222 | .swiper-rtl .swiper-button-prev {
223 | right: 10px;
224 | left: auto;
225 | }
226 | .swiper-button-next:after,
227 | .swiper-rtl .swiper-button-prev:after {
228 | content: 'next';
229 | }
230 | .swiper-button-lock {
231 | display: none;
232 | }
233 | :root {
234 | /*
235 | --swiper-pagination-color: var(--swiper-theme-color);
236 | --swiper-pagination-bullet-size: 8px;
237 | --swiper-pagination-bullet-width: 8px;
238 | --swiper-pagination-bullet-height: 8px;
239 | --swiper-pagination-bullet-inactive-color: #000;
240 | --swiper-pagination-bullet-inactive-opacity: 0.2;
241 | --swiper-pagination-bullet-opacity: 1;
242 | --swiper-pagination-bullet-horizontal-gap: 4px;
243 | --swiper-pagination-bullet-vertical-gap: 6px;
244 | */
245 | }
246 | .swiper-pagination {
247 | position: absolute;
248 | text-align: center;
249 | transition: 300ms opacity;
250 | transform: translate3d(0, 0, 0);
251 | z-index: 10;
252 | }
253 | .swiper-pagination.swiper-pagination-hidden {
254 | opacity: 0;
255 | }
256 | /* Common Styles */
257 | .swiper-pagination-fraction,
258 | .swiper-pagination-custom,
259 | .swiper-horizontal > .swiper-pagination-bullets,
260 | .swiper-pagination-bullets.swiper-pagination-horizontal {
261 | bottom: 10px;
262 | left: 0;
263 | width: 100%;
264 | }
265 | /* Bullets */
266 | .swiper-pagination-bullets-dynamic {
267 | overflow: hidden;
268 | font-size: 0;
269 | }
270 | .swiper-pagination-bullets-dynamic .swiper-pagination-bullet {
271 | transform: scale(0.33);
272 | position: relative;
273 | }
274 | .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active {
275 | transform: scale(1);
276 | }
277 | .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-main {
278 | transform: scale(1);
279 | }
280 | .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev {
281 | transform: scale(0.66);
282 | }
283 | .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-prev-prev {
284 | transform: scale(0.33);
285 | }
286 | .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next {
287 | transform: scale(0.66);
288 | }
289 | .swiper-pagination-bullets-dynamic .swiper-pagination-bullet-active-next-next {
290 | transform: scale(0.33);
291 | }
292 | .swiper-pagination-bullet {
293 | width: var(--swiper-pagination-bullet-width, var(--swiper-pagination-bullet-size, 8px));
294 | height: var(--swiper-pagination-bullet-height, var(--swiper-pagination-bullet-size, 8px));
295 | display: inline-block;
296 | border-radius: 50%;
297 | background: var(--swiper-pagination-bullet-inactive-color, #000);
298 | opacity: var(--swiper-pagination-bullet-inactive-opacity, 0.2);
299 | }
300 | button.swiper-pagination-bullet {
301 | border: none;
302 | margin: 0;
303 | padding: 0;
304 | box-shadow: none;
305 | -webkit-appearance: none;
306 | appearance: none;
307 | }
308 | .swiper-pagination-clickable .swiper-pagination-bullet {
309 | cursor: pointer;
310 | }
311 | .swiper-pagination-bullet:only-child {
312 | display: none !important;
313 | }
314 | .swiper-pagination-bullet-active {
315 | opacity: var(--swiper-pagination-bullet-opacity, 1);
316 | background: var(--swiper-pagination-color, var(--swiper-theme-color));
317 | }
318 | .swiper-vertical > .swiper-pagination-bullets,
319 | .swiper-pagination-vertical.swiper-pagination-bullets {
320 | right: 10px;
321 | top: 50%;
322 | transform: translate3d(0px, -50%, 0);
323 | }
324 | .swiper-vertical > .swiper-pagination-bullets .swiper-pagination-bullet,
325 | .swiper-pagination-vertical.swiper-pagination-bullets .swiper-pagination-bullet {
326 | margin: var(--swiper-pagination-bullet-vertical-gap, 6px) 0;
327 | display: block;
328 | }
329 | .swiper-vertical > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic,
330 | .swiper-pagination-vertical.swiper-pagination-bullets.swiper-pagination-bullets-dynamic {
331 | top: 50%;
332 | transform: translateY(-50%);
333 | width: 8px;
334 | }
335 | .swiper-vertical > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet,
336 | .swiper-pagination-vertical.swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet {
337 | display: inline-block;
338 | transition: 200ms transform, 200ms top;
339 | }
340 | .swiper-horizontal > .swiper-pagination-bullets .swiper-pagination-bullet,
341 | .swiper-pagination-horizontal.swiper-pagination-bullets .swiper-pagination-bullet {
342 | margin: 0 var(--swiper-pagination-bullet-horizontal-gap, 4px);
343 | }
344 | .swiper-horizontal > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic,
345 | .swiper-pagination-horizontal.swiper-pagination-bullets.swiper-pagination-bullets-dynamic {
346 | left: 50%;
347 | transform: translateX(-50%);
348 | white-space: nowrap;
349 | }
350 | .swiper-horizontal > .swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet,
351 | .swiper-pagination-horizontal.swiper-pagination-bullets.swiper-pagination-bullets-dynamic .swiper-pagination-bullet {
352 | transition: 200ms transform, 200ms left;
353 | }
354 | .swiper-horizontal.swiper-rtl > .swiper-pagination-bullets-dynamic .swiper-pagination-bullet {
355 | transition: 200ms transform, 200ms right;
356 | }
357 | /* Progress */
358 | .swiper-pagination-progressbar {
359 | background: rgba(0, 0, 0, 0.25);
360 | position: absolute;
361 | }
362 | .swiper-pagination-progressbar .swiper-pagination-progressbar-fill {
363 | background: var(--swiper-pagination-color, var(--swiper-theme-color));
364 | position: absolute;
365 | left: 0;
366 | top: 0;
367 | width: 100%;
368 | height: 100%;
369 | transform: scale(0);
370 | transform-origin: left top;
371 | }
372 | .swiper-rtl .swiper-pagination-progressbar .swiper-pagination-progressbar-fill {
373 | transform-origin: right top;
374 | }
375 | .swiper-horizontal > .swiper-pagination-progressbar,
376 | .swiper-pagination-progressbar.swiper-pagination-horizontal,
377 | .swiper-vertical > .swiper-pagination-progressbar.swiper-pagination-progressbar-opposite,
378 | .swiper-pagination-progressbar.swiper-pagination-vertical.swiper-pagination-progressbar-opposite {
379 | width: 100%;
380 | height: 4px;
381 | left: 0;
382 | top: 0;
383 | }
384 | .swiper-vertical > .swiper-pagination-progressbar,
385 | .swiper-pagination-progressbar.swiper-pagination-vertical,
386 | .swiper-horizontal > .swiper-pagination-progressbar.swiper-pagination-progressbar-opposite,
387 | .swiper-pagination-progressbar.swiper-pagination-horizontal.swiper-pagination-progressbar-opposite {
388 | width: 4px;
389 | height: 100%;
390 | left: 0;
391 | top: 0;
392 | }
393 | .swiper-pagination-lock {
394 | display: none;
395 | }
396 | /* Scrollbar */
397 | .swiper-scrollbar {
398 | border-radius: 10px;
399 | position: relative;
400 | -ms-touch-action: none;
401 | background: rgba(0, 0, 0, 0.1);
402 | }
403 | .swiper-horizontal > .swiper-scrollbar {
404 | position: absolute;
405 | left: 1%;
406 | bottom: 3px;
407 | z-index: 50;
408 | height: 5px;
409 | width: 98%;
410 | }
411 | .swiper-vertical > .swiper-scrollbar {
412 | position: absolute;
413 | right: 3px;
414 | top: 1%;
415 | z-index: 50;
416 | width: 5px;
417 | height: 98%;
418 | }
419 | .swiper-scrollbar-drag {
420 | height: 100%;
421 | width: 100%;
422 | position: relative;
423 | background: rgba(0, 0, 0, 0.5);
424 | border-radius: 10px;
425 | left: 0;
426 | top: 0;
427 | }
428 | .swiper-scrollbar-cursor-drag {
429 | cursor: move;
430 | }
431 | .swiper-scrollbar-lock {
432 | display: none;
433 | }
434 | .swiper-zoom-container {
435 | width: 100%;
436 | height: 100%;
437 | display: flex;
438 | justify-content: center;
439 | align-items: center;
440 | text-align: center;
441 | }
442 | .swiper-zoom-container > img,
443 | .swiper-zoom-container > svg,
444 | .swiper-zoom-container > canvas {
445 | max-width: 100%;
446 | max-height: 100%;
447 | object-fit: contain;
448 | }
449 | .swiper-slide-zoomed {
450 | cursor: move;
451 | }
452 | /* Preloader */
453 | :root {
454 | /*
455 | --swiper-preloader-color: var(--swiper-theme-color);
456 | */
457 | }
458 | .swiper-lazy-preloader {
459 | width: 42px;
460 | height: 42px;
461 | position: absolute;
462 | left: 50%;
463 | top: 50%;
464 | margin-left: -21px;
465 | margin-top: -21px;
466 | z-index: 10;
467 | transform-origin: 50%;
468 | animation: swiper-preloader-spin 1s infinite linear;
469 | box-sizing: border-box;
470 | border: 4px solid var(--swiper-preloader-color, var(--swiper-theme-color));
471 | border-radius: 50%;
472 | border-top-color: transparent;
473 | }
474 | .swiper-lazy-preloader-white {
475 | --swiper-preloader-color: #fff;
476 | }
477 | .swiper-lazy-preloader-black {
478 | --swiper-preloader-color: #000;
479 | }
480 | @keyframes swiper-preloader-spin {
481 | 100% {
482 | transform: rotate(360deg);
483 | }
484 | }
485 | /* a11y */
486 | .swiper .swiper-notification {
487 | position: absolute;
488 | left: 0;
489 | top: 0;
490 | pointer-events: none;
491 | opacity: 0;
492 | z-index: -1000;
493 | }
494 | .swiper-free-mode > .swiper-wrapper {
495 | transition-timing-function: ease-out;
496 | margin: 0 auto;
497 | }
498 | .swiper-grid > .swiper-wrapper {
499 | flex-wrap: wrap;
500 | }
501 | .swiper-grid-column > .swiper-wrapper {
502 | flex-wrap: wrap;
503 | flex-direction: column;
504 | }
505 | .swiper-fade.swiper-free-mode .swiper-slide {
506 | transition-timing-function: ease-out;
507 | }
508 | .swiper-fade .swiper-slide {
509 | pointer-events: none;
510 | transition-property: opacity;
511 | }
512 | .swiper-fade .swiper-slide .swiper-slide {
513 | pointer-events: none;
514 | }
515 | .swiper-fade .swiper-slide-active,
516 | .swiper-fade .swiper-slide-active .swiper-slide-active {
517 | pointer-events: auto;
518 | }
519 | .swiper-cube {
520 | overflow: visible;
521 | }
522 | .swiper-cube .swiper-slide {
523 | pointer-events: none;
524 | -webkit-backface-visibility: hidden;
525 | backface-visibility: hidden;
526 | z-index: 1;
527 | visibility: hidden;
528 | transform-origin: 0 0;
529 | width: 100%;
530 | height: 100%;
531 | }
532 | .swiper-cube .swiper-slide .swiper-slide {
533 | pointer-events: none;
534 | }
535 | .swiper-cube.swiper-rtl .swiper-slide {
536 | transform-origin: 100% 0;
537 | }
538 | .swiper-cube .swiper-slide-active,
539 | .swiper-cube .swiper-slide-active .swiper-slide-active {
540 | pointer-events: auto;
541 | }
542 | .swiper-cube .swiper-slide-active,
543 | .swiper-cube .swiper-slide-next,
544 | .swiper-cube .swiper-slide-prev,
545 | .swiper-cube .swiper-slide-next + .swiper-slide {
546 | pointer-events: auto;
547 | visibility: visible;
548 | }
549 | .swiper-cube .swiper-slide-shadow-top,
550 | .swiper-cube .swiper-slide-shadow-bottom,
551 | .swiper-cube .swiper-slide-shadow-left,
552 | .swiper-cube .swiper-slide-shadow-right {
553 | z-index: 0;
554 | -webkit-backface-visibility: hidden;
555 | backface-visibility: hidden;
556 | }
557 | .swiper-cube .swiper-cube-shadow {
558 | position: absolute;
559 | left: 0;
560 | bottom: 0px;
561 | width: 100%;
562 | height: 100%;
563 | opacity: 0.6;
564 | z-index: 0;
565 | }
566 | .swiper-cube .swiper-cube-shadow:before {
567 | content: '';
568 | background: #000;
569 | position: absolute;
570 | left: 0;
571 | top: 0;
572 | bottom: 0;
573 | right: 0;
574 | filter: blur(50px);
575 | }
576 | .swiper-flip {
577 | overflow: visible;
578 | }
579 | .swiper-flip .swiper-slide {
580 | pointer-events: none;
581 | -webkit-backface-visibility: hidden;
582 | backface-visibility: hidden;
583 | z-index: 1;
584 | }
585 | .swiper-flip .swiper-slide .swiper-slide {
586 | pointer-events: none;
587 | }
588 | .swiper-flip .swiper-slide-active,
589 | .swiper-flip .swiper-slide-active .swiper-slide-active {
590 | pointer-events: auto;
591 | }
592 | .swiper-flip .swiper-slide-shadow-top,
593 | .swiper-flip .swiper-slide-shadow-bottom,
594 | .swiper-flip .swiper-slide-shadow-left,
595 | .swiper-flip .swiper-slide-shadow-right {
596 | z-index: 0;
597 | -webkit-backface-visibility: hidden;
598 | backface-visibility: hidden;
599 | }
600 | .swiper-creative .swiper-slide {
601 | -webkit-backface-visibility: hidden;
602 | backface-visibility: hidden;
603 | overflow: hidden;
604 | transition-property: transform, opacity, height;
605 | }
606 | .swiper-cards {
607 | overflow: visible;
608 | }
609 | .swiper-cards .swiper-slide {
610 | transform-origin: center bottom;
611 | -webkit-backface-visibility: hidden;
612 | backface-visibility: hidden;
613 | overflow: hidden;
614 | }
615 |
--------------------------------------------------------------------------------
/stylelintconfig.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | rules: {
3 | 'font-family-no-duplicate-names': true,
4 | 'font-family-no-missing-generic-family-keyword': true,
5 | 'no-descending-specificity': null,
6 |
7 | 'color-hex-case': 'lower',
8 | 'color-hex-length': 'long',
9 |
10 | 'comment-no-empty': true,
11 |
12 | 'font-family-name-quotes': 'always-unless-keyword',
13 |
14 | 'function-comma-newline-after': 'always-multi-line',
15 | 'function-comma-newline-before': 'never-multi-line',
16 | 'function-comma-space-after': 'always-single-line',
17 | 'function-comma-space-before': 'never',
18 | 'function-max-empty-lines': 0,
19 | 'function-name-case': 'lower',
20 | 'function-parentheses-space-inside': 'never-single-line',
21 | 'function-url-quotes': 'always',
22 | 'function-whitespace-after': 'always',
23 | 'function-calc-no-invalid': true,
24 | 'function-calc-no-unspaced-operator': true,
25 | 'function-linear-gradient-no-nonstandard-direction': true,
26 |
27 | 'number-leading-zero': 'always',
28 | 'number-no-trailing-zeros': true,
29 | 'length-zero-no-unit': true,
30 |
31 | 'string-quotes': 'double',
32 |
33 | 'unit-case': 'lower',
34 |
35 | 'value-keyword-case': 'lower',
36 | 'value-list-comma-newline-after': 'always-multi-line',
37 | 'value-list-comma-newline-before': 'never-multi-line',
38 | 'value-list-comma-space-after': 'always-single-line',
39 | 'value-list-comma-space-before': 'never',
40 | 'value-list-max-empty-lines': 0,
41 | 'value-no-vendor-prefix': true,
42 |
43 | 'property-case': 'lower',
44 | 'property-no-vendor-prefix': true,
45 |
46 | 'keyframe-declaration-no-important': true,
47 | 'declaration-no-important': true,
48 |
49 | 'declaration-bang-space-after': 'never',
50 | 'declaration-bang-space-before': 'always',
51 | 'declaration-colon-space-after': 'always-single-line',
52 | 'declaration-colon-space-before': 'never',
53 |
54 | 'declaration-block-no-duplicate-custom-properties': true,
55 | 'declaration-block-no-shorthand-property-overrides': true,
56 | 'declaration-block-no-duplicate-properties': true,
57 | 'declaration-block-semicolon-newline-after': 'always',
58 | 'declaration-block-semicolon-newline-before': 'never-multi-line',
59 | 'declaration-block-semicolon-space-after': 'always-single-line',
60 | 'declaration-block-semicolon-space-before': 'never',
61 | 'declaration-block-trailing-semicolon': 'always',
62 |
63 | 'block-closing-brace-empty-line-before': 'never',
64 | 'block-closing-brace-newline-after': 'always',
65 | 'block-closing-brace-newline-before': 'always',
66 | 'block-closing-brace-space-after': 'always-single-line',
67 | 'block-closing-brace-space-before': 'always-single-line',
68 | 'block-opening-brace-newline-after': 'always',
69 | 'block-opening-brace-space-after': 'always-single-line',
70 | 'block-opening-brace-space-before': 'always',
71 | 'block-no-empty': true,
72 |
73 | 'selector-attribute-brackets-space-inside': 'never',
74 | 'selector-attribute-operator-space-after': 'never',
75 | 'selector-attribute-operator-space-before': 'never',
76 | 'selector-attribute-quotes': 'always',
77 | 'selector-combinator-space-after': 'always',
78 | 'selector-combinator-space-before': 'always',
79 | 'selector-descendant-combinator-no-non-space': true,
80 | 'selector-max-id': 0,
81 | 'selector-pseudo-class-case': 'lower',
82 | 'selector-pseudo-class-parentheses-space-inside': 'never',
83 | 'selector-pseudo-element-case': 'lower',
84 | 'selector-pseudo-element-colon-notation': 'double',
85 | 'selector-type-case': 'lower',
86 | 'selector-type-no-unknown': true,
87 | 'selector-max-empty-lines': 0,
88 |
89 | 'selector-list-comma-newline-after': 'always',
90 | 'selector-list-comma-newline-before': 'never-multi-line',
91 | 'selector-list-comma-space-after': 'always-single-line',
92 | 'selector-list-comma-space-before': 'never',
93 |
94 | 'rule-empty-line-before': [
95 | 'always',
96 | {
97 | 'except': ['first-nested'],
98 | 'ignore': ['after-comment'],
99 | }
100 | ],
101 |
102 | 'media-feature-colon-space-after': 'always',
103 | 'media-feature-colon-space-before': 'never',
104 | 'media-feature-name-case': 'lower',
105 | 'media-feature-name-no-vendor-prefix': true,
106 | 'media-feature-parentheses-space-inside': 'never',
107 | 'media-feature-range-operator-space-after': 'always',
108 | 'media-feature-range-operator-space-before': 'always',
109 | 'media-feature-name-no-unknown': true,
110 |
111 | 'media-query-list-comma-newline-after': 'always-multi-line',
112 | 'media-query-list-comma-newline-before': 'never-multi-line',
113 | 'media-query-list-comma-space-after': 'always-single-line',
114 | 'media-query-list-comma-space-before': 'never-single-line',
115 |
116 | 'at-rule-empty-line-before': [
117 | 'always',
118 | {
119 | 'except': ['first-nested', 'blockless-after-blockless'],
120 | 'ignore': ['after-comment'],
121 | }
122 | ],
123 | 'at-rule-name-case': 'lower',
124 | 'at-rule-name-space-after': 'always',
125 | 'at-rule-no-unknown': [
126 | true,
127 | {
128 | 'ignoreAtRules': ['mixin', 'define-mixin', 'include', 'content', 'rules', 'each'],
129 | }
130 | ],
131 | 'at-rule-no-vendor-prefix': true,
132 | 'at-rule-semicolon-newline-after': 'always',
133 |
134 | 'indentation': [2, {'ignore': ['inside-parens']}],
135 |
136 | 'max-nesting-depth': [4, {'ignoreAtRules': ['media', 'include']}],
137 |
138 | 'max-empty-lines': 2,
139 | 'no-eol-whitespace': true,
140 | 'no-missing-end-of-source-newline': true,
141 |
142 | 'no-duplicate-at-import-rules': true,
143 | 'no-invalid-position-at-import-rule': true,
144 |
145 | 'named-grid-areas-no-invalid': true,
146 | 'no-duplicate-selectors': true,
147 | 'no-empty-source': true,
148 | 'no-extra-semicolons': true,
149 | 'no-invalid-double-slash-comments': true,
150 | 'no-irregular-whitespace': true,
151 | 'property-no-unknown': true,
152 | 'selector-pseudo-class-no-unknown': true,
153 | 'selector-pseudo-element-no-unknown': true,
154 |
155 | 'string-no-newline': true,
156 | 'unit-no-unknown': true,
157 |
158 | 'color-no-invalid-hex': true,
159 | 'color-no-hex': null,
160 | 'color-named': 'never',
161 | 'number-max-precision': 2,
162 | },
163 | };
164 |
--------------------------------------------------------------------------------
/webpack.config.cjs:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const CircularDependencyPlugin = require('circular-dependency-plugin')
3 | const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
4 | const {CleanWebpackPlugin} = require('clean-webpack-plugin');
5 | const isProd = process.env.NODE_ENV === 'production';
6 | const isDev = !isProd;
7 |
8 | module.exports = {
9 | context: path.resolve(__dirname, 'source'),
10 | mode: 'development',
11 | entry: {
12 | main: './js/main.js',
13 | vendor: './js/vendor.js',
14 | },
15 | devtool: isDev ? 'source-map' : false,
16 | output: {
17 | filename: '[name].min.js',
18 | path: path.resolve(__dirname, 'build/js'),
19 | },
20 | optimization: {
21 | minimize: isDev ? false : true,
22 | },
23 | module: {
24 | rules: [
25 | {
26 | test: /\.js$/,
27 | exclude: /node_modules/,
28 | loader: 'babel-loader',
29 | options: {
30 | presets: ['@babel/preset-env'],
31 | },
32 | },
33 | ],
34 | },
35 | plugins: [
36 | new CleanWebpackPlugin(),
37 | new DuplicatePackageCheckerPlugin(),
38 | new CircularDependencyPlugin(),
39 | ],
40 | };
41 |
--------------------------------------------------------------------------------