├── .gitignore ├── public ├── favicon.ico ├── img │ ├── avatar.jpg │ ├── deploy.png │ ├── index.jpg │ ├── server.jpg │ └── icons │ │ ├── icon_tg.png │ │ ├── icon_vk.png │ │ ├── icon_github.png │ │ └── icon_discord.png └── css │ ├── config-page.css │ └── index-page.css ├── routes ├── index.js └── generateConfigNgnix.js ├── package.json ├── index.js ├── views ├── generateConfig.ejs └── index.ejs ├── utils └── configNgnixGenerate.js └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomBaytovich/example_deploy_proj/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/img/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomBaytovich/example_deploy_proj/HEAD/public/img/avatar.jpg -------------------------------------------------------------------------------- /public/img/deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomBaytovich/example_deploy_proj/HEAD/public/img/deploy.png -------------------------------------------------------------------------------- /public/img/index.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomBaytovich/example_deploy_proj/HEAD/public/img/index.jpg -------------------------------------------------------------------------------- /public/img/server.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomBaytovich/example_deploy_proj/HEAD/public/img/server.jpg -------------------------------------------------------------------------------- /public/img/icons/icon_tg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomBaytovich/example_deploy_proj/HEAD/public/img/icons/icon_tg.png -------------------------------------------------------------------------------- /public/img/icons/icon_vk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomBaytovich/example_deploy_proj/HEAD/public/img/icons/icon_vk.png -------------------------------------------------------------------------------- /public/img/icons/icon_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomBaytovich/example_deploy_proj/HEAD/public/img/icons/icon_github.png -------------------------------------------------------------------------------- /public/img/icons/icon_discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AtomBaytovich/example_deploy_proj/HEAD/public/img/icons/icon_discord.png -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | router.get('/', (req, res) => { 5 | return res.render('index', { 6 | title: 'Мини сайт Atom Baytovich | CODE' 7 | }); 8 | }); 9 | 10 | 11 | module.exports = router; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-deploy-proj", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon index.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/AtomBaytovich/example_deploy_proj.git" 12 | }, 13 | "author": "Atom Baytovich", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/AtomBaytovich/example_deploy_proj/issues" 17 | }, 18 | "homepage": "https://github.com/AtomBaytovich/example_deploy_proj#readme", 19 | "dependencies": { 20 | "body-parser": "^1.20.0", 21 | "content-disposition": "^0.5.4", 22 | "cors": "^2.8.5", 23 | "ejs": "^3.1.8", 24 | "express": "^4.18.1", 25 | "punycode": "^2.1.1" 26 | }, 27 | "devDependencies": { 28 | "nodemon": "^2.0.19" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const cors = require('cors'); 4 | const bodyParser = require('body-parser'); 5 | 6 | 7 | const PORT = process.env.PORT || 3000; 8 | 9 | const app = express(); 10 | 11 | // cors 12 | app.use(cors()); 13 | app.options('*', cors()); 14 | 15 | // parse application/x-www-form-urlencoded 16 | app.use(bodyParser.urlencoded({ extended: false })) 17 | 18 | // parse application/json 19 | app.use(bodyParser.json()) 20 | 21 | // шаблонизатор ejs 22 | app.set('views', './views'); 23 | app.set('view engine', 'ejs'); 24 | 25 | // статическая папка 26 | app.use(express.static(path.join(__dirname, 'public'))); 27 | 28 | // подключаем наши роуты 29 | app.use('/', require('./routes/index')); 30 | app.use('/config-ngnix', require('./routes/generateConfigNgnix')); 31 | // Not Found 32 | app.use("*", (req, res) => res.send('404 | Not Found')); 33 | 34 | // прослушка 35 | app.listen(PORT, () => { 36 | console.log(`Проект запущен на http://localhost:${PORT}`); 37 | }); -------------------------------------------------------------------------------- /routes/generateConfigNgnix.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const punycode = require('punycode'); 4 | const { generateConfig } = require("../utils/configNgnixGenerate"); 5 | const stream = require('stream'); 6 | const contentDisposition = require('content-disposition') 7 | 8 | router.get('/', (req, res) => { 9 | return res.render('generateConfig', { 10 | title: 'Генерация конфига Ngnix' 11 | }); 12 | }); 13 | 14 | router.post('/', (req, res) => { 15 | const { domain } = req.body; 16 | const fileName = `${domain}.txt`; 17 | let domainEncode = punycode.toASCII(domain); 18 | 19 | const configText = generateConfig({ 20 | domain: domainEncode 21 | }) 22 | 23 | const fileContents = Buffer.from(configText, "utf-8"); 24 | const readStream = new stream.PassThrough(); 25 | 26 | readStream.end(fileContents); 27 | res.setHeader('Content-Disposition', contentDisposition(fileName)) 28 | res.set('Content-Type', 'text/plain'); 29 | readStream.pipe(res); 30 | }) 31 | 32 | 33 | module.exports = router; -------------------------------------------------------------------------------- /views/generateConfig.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= title %> 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | Deploy img 19 |

Быстрый генератор конфига Ngnix с ТГ канала Atom Baytovich

20 |
21 | 22 |
23 | 26 |
27 |
28 | На главную 29 |
30 |
31 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /utils/configNgnixGenerate.js: -------------------------------------------------------------------------------- 1 | /// xn--l1abdi.com на свой домен 2 | /// конфигурация с редиректом на https и с www 3 | /// TG канал: @atom_baytovich 4 | 5 | const generateConfig = ({ domain = 'xn--l1abdi.com' }) => { 6 | return ` 7 | server { 8 | listen 443 ssl http2; 9 | listen [::]:443 ssl http2; 10 | server_name ${domain}; 11 | # SSL 12 | ssl_certificate /etc/letsencrypt/live/${domain}/fullchain.pem; 13 | ssl_certificate_key /etc/letsencrypt/live/${domain}/privkey.pem; 14 | ssl_trusted_certificate /etc/letsencrypt/live/${domain}/chain.pem; 15 | include /etc/letsencrypt/options-ssl-nginx.conf; 16 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 17 | 18 | # reverse proxy 19 | location / { 20 | proxy_pass http://localhost:3000; 21 | proxy_http_version 1.1; 22 | proxy_set_header Upgrade $http_upgrade; 23 | proxy_set_header Connection 'upgrade'; 24 | proxy_set_header Host $host; 25 | proxy_cache_bypass $http_upgrade; 26 | } 27 | 28 | } 29 | 30 | # subdomains redirect 31 | server { 32 | listen 443 ssl http2; 33 | listen [::]:443 ssl http2; 34 | server_name *.${domain}; 35 | # SSL 36 | ssl_certificate /etc/letsencrypt/live/${domain}/fullchain.pem; 37 | ssl_certificate_key /etc/letsencrypt/live/${domain}/privkey.pem; 38 | ssl_trusted_certificate /etc/letsencrypt/live/${domain}/chain.pem; 39 | return 301 https://${domain}$request_uri; 40 | } 41 | 42 | # HTTP redirect 43 | server { 44 | listen 80; 45 | listen [::]:80; 46 | server_name .${domain}; 47 | 48 | location / { 49 | return 301 https://${domain}$request_uri; 50 | } 51 | } 52 | ` 53 | } 54 | 55 | module.exports = { 56 | generateConfig 57 | } -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= title %> 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | Avatar img 19 |
20 |

21 | Atom Baytovich 22 |

23 | 71 |
72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /public/css/config-page.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,400;0,500;1,300&display=swap'); 2 | 3 | html, body, div, span, applet, object, iframe, 4 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 5 | a, abbr, acronym, address, big, cite, code, 6 | del, dfn, em, img, ins, kbd, q, s, samp, 7 | small, strike, strong, sub, sup, tt, var, 8 | b, u, i, center, 9 | dl, dt, dd, ol, ul, li, 10 | fieldset, form, label, legend, 11 | table, caption, tbody, tfoot, thead, tr, th, td, 12 | article, aside, canvas, details, embed, 13 | figure, figcaption, footer, header, hgroup, 14 | menu, nav, output, ruby, section, summary, 15 | time, mark, audio, video { 16 | margin: 0; 17 | padding: 0; 18 | border: 0; 19 | font-size: 100%; 20 | font: inherit; 21 | vertical-align: baseline; 22 | } 23 | 24 | article, aside, details, figcaption, figure, 25 | footer, header, hgroup, menu, nav, section { 26 | display: block; 27 | } 28 | 29 | html { 30 | height: 100%; 31 | } 32 | 33 | body { 34 | line-height: 1; 35 | font-family: 'Montserrat', sans-serif; 36 | background: url(../img/server.jpg) no-repeat 50% fixed; 37 | background-size: cover !important; 38 | overflow: hidden; 39 | } 40 | 41 | ol, ul { 42 | list-style: none; 43 | } 44 | 45 | blockquote, q { 46 | quotes: none; 47 | } 48 | 49 | blockquote:before, blockquote:after, 50 | q:before, q:after { 51 | content: ''; 52 | content: none; 53 | } 54 | 55 | table { 56 | border-collapse: collapse; 57 | border-spacing: 0; 58 | } 59 | 60 | input { 61 | all: unset; 62 | } 63 | 64 | button { 65 | padding: 0; 66 | border: none; 67 | font: inherit; 68 | color: inherit; 69 | background-color: transparent; 70 | /* отображаем курсор в виде руки при наведении; некоторые 71 | считают, что необходимо оставлять стрелочный вид для кнопок */ 72 | cursor: pointer; 73 | } 74 | 75 | /* закончились обнуляющие стили */ 76 | 77 | a { 78 | color: #fff; 79 | /* text-decoration: ; */ 80 | } 81 | 82 | .wrapper { 83 | min-height: 100vh; 84 | display: flex; 85 | align-items: center; 86 | color: #fff !important; 87 | } 88 | 89 | .main { 90 | max-width: 360px; 91 | max-height: 500px; 92 | padding: 5px; 93 | display: flex; 94 | align-items: center; 95 | flex-direction: column; 96 | background-color: #1e1e1e66; 97 | backdrop-filter: blur(30px); 98 | box-shadow: #04040496 1px 1px 3px 2px; 99 | margin: 0 auto; 100 | border-radius: 10px; 101 | } 102 | 103 | .main p { 104 | margin-top: 10px; 105 | margin-bottom: 20px; 106 | text-align: center; 107 | font-size: 17px; 108 | } 109 | 110 | .form__main { 111 | display: flex; 112 | flex-direction: column; 113 | } 114 | 115 | .form__main input { 116 | width: 250px; 117 | height: 30px; 118 | border-radius: 5px; 119 | background-color: #04040496; 120 | margin-bottom: 10px; 121 | text-indent: 5px; 122 | } 123 | 124 | .form__main button { 125 | width: 150px; 126 | border-radius: 5px; 127 | font-size: 14px; 128 | background-color: #1e1e1e66; 129 | height: 25px; 130 | 131 | } 132 | 133 | .form__main .button { 134 | margin: 0 auto; 135 | margin-bottom: 20px; 136 | } 137 | 138 | .href { 139 | margin-bottom: 10px; 140 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 👨‍💻 Как правильно деплоить сайт? Node.js (Nginx и SSL Let's Encrypt), PM2 + GIT bonuse 2 | 3 | В этом гайде я расскажу как правильно задеплоить сайт на хостинг с доменом, настроим SSL сертификат для HTTPS протокола (certbot). Познакомимся с NGNIX и PM2. 4 | 5 | Для примера проекта, который будет здесь, я создал простейший сайт на express (свою визитку). 6 | Т.к. я не ux/ui designer, то дизайн не супер-пупер (простите). 7 | 8 | [Online demo: норм.com](https://норм.com) 9 | 10 | [Мой авторский TG канал](https://t.me/atom_baytovich) 11 | 12 | ## Начало 13 | (чуть сухой терминологии) 14 | - pm2 - это менеджер процессов, который поможет вам управлять вашим приложением и поддерживать его онлайн 24/7. 15 | - ngnix - это веб-сервер и почтовый прокси-сервер, работающий на Unix-подобных операционных системах. Имеет неблокирующий ввод/вывод. (поэтому такой быстрый) (кстати, его создатель - это разработчик из России) 16 | - https - это расширение протокола HTTP. Оно позволяет существенно снизить риск перехвата персональных данных посетителей (логины, пароли, номера банковских карт и т. д.), а также избежать подмены контента, в том числе рекламы, при загрузке сайта. 17 | - ssl (Secure Sockets Laye) - это цифровой сертификат, удостоверяющий подлинность веб-сайта и позволяющий использовать зашифрованное соединение. 18 | 19 | 📌 Для того, чтобы залить сайт, нам нужны ДОМЕН и VPS-сервер (в нашем случае UBUNTU 20.04). 20 | Я буду использовать [reg.ru (!не реклама!)](https://www.reg.ru/?rlink=reflink-10083843) 21 | 22 | 📌 Программы, которые я буду использовать: [TERMIUS - ssh](https://termius.com/) и [FileZilla - ftp](https://www.filezilla.ru/) 23 | 24 | [Как купить / настроить домен](https://help.reg.ru/hc/ru/articles/4408047000977-%D0%9A%D0%B0%D0%BA-%D0%BF%D1%80%D0%B8%D0%B2%D1%8F%D0%B7%D0%B0%D1%82%D1%8C-%D0%B4%D0%BE%D0%BC%D0%B5%D0%BD-%D0%BA-VPS) на vps рассказано у [reg.ru](https://www.reg.ru/?rlink=reflink-10083843) в статьях. 25 | Примечание* создание записи в dns сервера к домену произойдёт не сразу. 26 | 27 | ## Продолжаем 28 | 29 | 🥷 После покупки сервера с доменом и настройки домена на dns, мы можем приступить к "внутрянке". 30 | 31 | 1. Зайдём на наш vps-сервер по ssh и обнаружим, что он вовсе пустой. 32 | 2. Установим Node.js первым делом 33 | 34 | ```ssh 35 | sudo apt update # обновление состояние пакетов 36 | curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash - 37 | sudo apt install nodejs 38 | ``` 39 | 3. После установки проверим версии Node.js и npm (должны видны версии в случае успеха) 40 | ```ssh 41 | node --version 42 | npm --version 43 | ``` 44 | 4. Установим ngnix 45 | ```ssh 46 | sudo apt install nginx # Отвечаем 'y' 47 | 48 | ``` 49 | 5. Установим SSL сертфиката Let's Encrypt 50 | ```ssh 51 | sudo apt install certbot python3-certbot-nginx # Отвечаем 'y' 52 | ``` 53 | 6. Настроим Certbot и автообновление сертификата 54 | ```ssh 55 | sudo certbot --nginx -d домен.com -d www.домен.com 56 | certbot renew --dry-run # это автообновление 57 | ``` 58 | 7. Далее зайдём в настройку виртуальных хостов 59 | 60 | ```ssh 61 | sudo nano /etc/nginx/sites-available/default 62 | ``` 63 | 8. Поля конфигурации полностью изменить на: [полная конфигурация редиректов с www и https в тг](https://t.me/atom_baytovich/17) 64 | 9. Ctrl + X чтобы выйти, Ctrl + X чтобы сохранить и после нажать клавишу Enter. И перезагрузить Ngnix 65 | ```ssh 66 | sudo service nginx restart 67 | ``` 68 | 69 | 10. ✨ БОНУС установка GIT 70 | ```ssh 71 | sudo apt update 72 | sudo apt install git 73 | ``` 74 | * Настройка git 75 | ``` 76 | git config --global user.name "Your Name" 77 | git config --global user.email "youremail@domain.com" 78 | ``` 79 | * После клонируем проект с GitHub и стандартные команды 80 | ```ssh 81 | git clone 'url' 82 | ``` 83 | 84 | #### _Вуаля, готово. После обращаемся к самому проекту_ 85 | 86 | ## Перейдём к PM2 87 | Его установка глобально: 88 | 89 | ```ssh 90 | npm install pm2 -g 91 | ``` 92 | 93 | Запуск проекта: 94 | ```ssh 95 | pm2 start index.js 96 | ``` 97 | 98 | Остановка проекта: 99 | ```ssh 100 | pm2 stop index.js 101 | ``` 102 | 103 | Посмотреть логи: 104 | ```ssh 105 | pm2 logs 106 | ``` 107 | 108 | Посмотреть статус всех приложений pm2: 109 | ```ssh 110 | pm2 status 111 | ``` 112 | 113 | 114 | ## ⭐️ Ну вот и всё. Спасибо за внимание! 115 | Подписывайся на [мой тг](https://t.me/atom_baytovich) и поставь звёздочку репозиторию. Я старался 😘 116 | -------------------------------------------------------------------------------- /public/css/index-page.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,400;0,500;1,300&display=swap'); 2 | 3 | html, body, div, span, applet, object, iframe, 4 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 5 | a, abbr, acronym, address, big, cite, code, 6 | del, dfn, em, img, ins, kbd, q, s, samp, 7 | small, strike, strong, sub, sup, tt, var, 8 | b, u, i, center, 9 | dl, dt, dd, ol, ul, li, 10 | fieldset, form, label, legend, 11 | table, caption, tbody, tfoot, thead, tr, th, td, 12 | article, aside, canvas, details, embed, 13 | figure, figcaption, footer, header, hgroup, 14 | menu, nav, output, ruby, section, summary, 15 | time, mark, audio, video { 16 | margin: 0; 17 | padding: 0; 18 | border: 0; 19 | font-size: 100%; 20 | font: inherit; 21 | vertical-align: baseline; 22 | } 23 | 24 | article, aside, details, figcaption, figure, 25 | footer, header, hgroup, menu, nav, section { 26 | display: block; 27 | } 28 | 29 | html { 30 | height: 100%; 31 | } 32 | 33 | body { 34 | line-height: 1; 35 | font-family: 'Montserrat', sans-serif; 36 | background: url(../img/server.jpg) no-repeat 50% fixed; 37 | background-size: cover !important; 38 | overflow: hidden; 39 | } 40 | 41 | ol, ul { 42 | list-style: none; 43 | } 44 | 45 | blockquote, q { 46 | quotes: none; 47 | } 48 | 49 | blockquote:before, blockquote:after, 50 | q:before, q:after { 51 | content: ''; 52 | content: none; 53 | } 54 | 55 | table { 56 | border-collapse: collapse; 57 | border-spacing: 0; 58 | } 59 | 60 | /* закончились обнуляющие стили */ 61 | 62 | .wrapper { 63 | min-height: 100vh; 64 | display: flex; 65 | align-items: center; 66 | color: #fff; 67 | } 68 | 69 | .main { 70 | max-width: 300px; 71 | padding: 0 20px 30px 20px; 72 | border-radius: 30px; 73 | margin: 0 auto; 74 | background-color: #1e1e1e66; 75 | backdrop-filter: blur(10px); 76 | box-shadow: #04040496 1px 1px 3px 2px; 77 | } 78 | 79 | .menu { 80 | text-align: center; 81 | } 82 | 83 | .years { 84 | font-size: 17px; 85 | font-weight: 500; 86 | margin-bottom: 10px; 87 | line-height: 18px; 88 | } 89 | 90 | .desc p { 91 | font-size: 17px; 92 | margin-bottom: 5px; 93 | line-height: 18px; 94 | } 95 | 96 | .contact-list { 97 | font-size: 17px; 98 | font-weight: 500; 99 | margin-top: 20px; 100 | } 101 | 102 | .socials-buttons { 103 | width: 100%; 104 | display: flex; 105 | margin-top: 20px; 106 | } 107 | 108 | .socials-link { 109 | margin-top: 20px; 110 | } 111 | 112 | 113 | /* АВАТАР */ 114 | .avatar { 115 | height: 180px; 116 | } 117 | 118 | .avatar img { 119 | display: block; 120 | margin: 0 auto; 121 | height: 220px; 122 | width: 220px; 123 | border-radius: 60px; 124 | box-shadow: #8a8a8a96 1px 1px 3px 2px; 125 | position: relative; 126 | top: -60px; 127 | } 128 | 129 | /* СОЦИАЛЬНЫЙ БЛОК */ 130 | 131 | .social-block { 132 | padding: 14px 4px 4px 4px; 133 | margin: 4px; 134 | display: flex; 135 | background: var(--back-color); 136 | color: #fff; 137 | cursor: pointer; 138 | border-radius: 10px; 139 | width: 256px; 140 | text-align: center; 141 | text-decoration: none; 142 | transition: .2s; 143 | border: var(--border); 144 | flex-direction: column; 145 | align-items: center; 146 | font-size: 14px; 147 | box-shadow: rgba(210, 120, 120, 0.59) 1px 1px 2px 2px; 148 | } 149 | 150 | .social-block img { 151 | color: #fff; 152 | cursor: pointer; 153 | text-align: center; 154 | font-size: 14px; 155 | height: 50px; 156 | width: 50px; 157 | margin-bottom: 8px; 158 | } 159 | 160 | .social-block:hover { 161 | box-shadow: rgba(193, 152, 152, 0.59) 1px 1px 2px 2px; 162 | } 163 | 164 | /* LINKS BLOCK */ 165 | 166 | .block-link { 167 | margin-top: 10px; 168 | width: 100%; 169 | display: flex; 170 | padding: 0px; 171 | border-radius: 8px; 172 | transition: .2s; 173 | box-shadow: rgba(210, 120, 120, 0.59) 1px 1px 2px 2px; 174 | } 175 | 176 | .block-link a { 177 | padding: 8px 20px; 178 | color: #fff; 179 | cursor: pointer; 180 | border-radius: 8px; 181 | width: 250px; 182 | text-align: center; 183 | text-decoration: none; 184 | } 185 | 186 | .block-link:hover { 187 | box-shadow: rgba(193, 152, 152, 0.59) 1px 1px 2px 2px; 188 | } 189 | 190 | /* TITLE */ 191 | 192 | 193 | .title { 194 | font-size: 30px; 195 | text-align: center; 196 | margin-bottom: 10px; 197 | } --------------------------------------------------------------------------------