Enhance project structure and styling for landing page. Add detailed file structure documentation in cloud.md. Update webpack configuration to support CSS Modules and SCSS. Introduce new styles for terms page and integrate them into the build process. Update package.json and package-lock.json with new dependencies for improved styling capabilities.
All checks were successful
platform/bro-js/bro.landing/pipeline/head This commit looks good

This commit is contained in:
Primakov Alexandr Alexandrovich 2025-10-24 14:19:58 +03:00
parent 6e55a331cb
commit ebf0daacce
11 changed files with 823 additions and 120 deletions

View File

@ -33,6 +33,32 @@
---
## Структура файлов
```
bro.landing/
├── src/
│ ├── styles/
│ │ ├── main.module.scss # CSS Modules для React компонентов
│ │ ├── main.module.scss.d.ts # TypeScript типы для CSS Modules
│ │ └── terms.scss # Обычный SCSS для статической страницы
│ ├── pages/
│ │ └── under-construction/ # Главная страница (React)
│ ├── index.tsx # Entry point для главной страницы
│ ├── terms.js # Entry point для стилей Terms
│ ├── terms.html # Статический HTML Terms страницы
│ ├── index.ejs # HTML шаблон для React страницы
│ └── app.tsx # React приложение
├── dist/ # Собранные файлы
│ ├── index.html # Главная (с React)
│ ├── index.js + index.css # Бандлы главной
│ ├── terms.html # Terms (чистый HTML + CSS)
│ └── terms.css # Стили Terms
├── ijl.config.js # Webpack конфигурация
└── package.json
```
## Структура проекта
```

View File

@ -3,6 +3,7 @@
const path = require('path');
const pkg = require('./package');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const webpack = require('webpack');
const isProd = process.env.NODE_ENV === 'production';
@ -18,7 +19,7 @@ module.exports = {
webpackConfig: {
entry: {
index: './src/index.tsx',
// terms страница не нужен JS, только HTML
terms: './src/terms.js', // Entry для стилей terms
},
output: {
publicPath: isProd
@ -26,6 +27,34 @@ module.exports = {
: `/static/${pkg.name}/${process.env.VERSION || pkg.version}/`,
filename: '[name].js?[contenthash]',
},
module: {
rules: [
{
test: /\.module\.scss$/,
use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: isProd ? '[hash:base64:8]' : '[name]__[local]--[hash:base64:5]',
},
},
},
'sass-loader'
],
},
{
test: /\.scss$/,
exclude: /\.module\.scss$/,
use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'sass-loader'
],
},
],
},
plugins: [
// Главная страница (с React)
new HtmlWebpackPlugin({
@ -33,12 +62,21 @@ module.exports = {
filename: 'index.html',
chunks: ['index'],
}),
// Terms страница (чистый HTML без JS)
// Terms страница (статика + SCSS)
new HtmlWebpackPlugin({
template: './src/terms.html',
filename: 'terms.html',
inject: false, // Не инжектим JS
chunks: isProd ? [] : ['terms'], // В production не нужен JS
cssPath: isProd
? 'https://static.brojs.ru/landing/main/terms.css'
: `/static/${pkg.name}/${process.env.VERSION || pkg.version}/terms.css`,
}),
// Извлечение CSS в отдельные файлы для production
...(isProd ? [
new MiniCssExtractPlugin({
filename: '[name].css',
})
] : []),
new webpack.DefinePlugin({
IS_PROD: process.env.NODE_ENV === 'production',
}),

457
package-lock.json generated
View File

@ -32,13 +32,18 @@
"@eslint/js": "^9.9.0",
"@types/jest": "^29.5.12",
"babel-jest": "^29.7.0",
"css-loader": "^7.1.2",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"globals": "^15.9.0",
"html-webpack-plugin": "^5.6.0",
"jest": "^29.7.0",
"mini-css-extract-plugin": "^2.9.4",
"puppeteer": "^24.26.1",
"sass": "^1.93.2",
"sass-loader": "^16.0.6",
"style-loader": "^4.0.0",
"ts-jest": "^29.2.3",
"typescript-eslint": "^8.1.0"
}
@ -3144,6 +3149,316 @@
"node": ">= 8"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"detect-libc": "^1.0.3",
"is-glob": "^4.0.3",
"micromatch": "^4.0.5",
"node-addon-api": "^7.0.0"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1"
}
},
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -5607,6 +5922,20 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@ -8024,6 +8353,13 @@
"url": "https://opencollective.com/immer"
}
},
"node_modules/immutable": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
"dev": true,
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -9839,6 +10175,27 @@
"node": ">=6"
}
},
"node_modules/mini-css-extract-plugin": {
"version": "2.9.4",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz",
"integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"schema-utils": "^4.0.0",
"tapable": "^2.2.1"
},
"engines": {
"node": ">= 12.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.0.0"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -9943,6 +10300,14 @@
"tslib": "^2.0.3"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@ -11698,6 +12063,98 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/sass": {
"version": "1.93.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz",
"integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
},
"optionalDependencies": {
"@parcel/watcher": "^2.4.1"
}
},
"node_modules/sass-loader": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.6.tgz",
"integrity": "sha512-sglGzId5gmlfxNs4gK2U3h7HlVRfx278YK6Ono5lwzuvi1jxig80YiuHkaDBVsYIKFhx8wN7XSCI0M2IDS/3qA==",
"dev": true,
"license": "MIT",
"dependencies": {
"neo-async": "^2.6.2"
},
"engines": {
"node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"@rspack/core": "0.x || 1.x",
"node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
"sass": "^1.3.0",
"sass-embedded": "*",
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
"@rspack/core": {
"optional": true
},
"node-sass": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"webpack": {
"optional": true
}
}
},
"node_modules/sass/node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/sass/node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",

View File

@ -42,13 +42,18 @@
"@eslint/js": "^9.9.0",
"@types/jest": "^29.5.12",
"babel-jest": "^29.7.0",
"css-loader": "^7.1.2",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"globals": "^15.9.0",
"html-webpack-plugin": "^5.6.0",
"jest": "^29.7.0",
"mini-css-extract-plugin": "^2.9.4",
"puppeteer": "^24.26.1",
"sass": "^1.93.2",
"sass-loader": "^16.0.6",
"style-loader": "^4.0.0",
"ts-jest": "^29.2.3",
"typescript-eslint": "^8.1.0"
}

140
readme.md
View File

@ -1,45 +1,129 @@
# BROJS.RU Landing Page
# BROJS.RU Landing
Лендинг платформы обучения фронтенд-разработке.
Лендинг платформы для обучения фронтенд-разработке
## 🚀 Быстрый старт
## 🚀 Особенности v3.0
- ⚡ **Минимальные зависимости** - только необходимое
- 📄 **Статический HTML** для идеального SEO
- 🎨 **SCSS + CSS Modules** для гибких стилей
- 📱 **Адаптивный дизайн** с responsive шрифтами
- 🎯 **React** только там, где нужна динамика
- 🔥 **Легкий bundle** terms.html (10 KB + 1.5 KB CSS, **без JS!**)
## 📦 Установка
```bash
# Установка
npm install
```
# Разработка
npm start
# → http://localhost:8099/
## 🛠️ Разработка
# Сборка
npm run build:prod
```bash
npm start # Dev сервер на http://localhost:8099
```
## 🏗️ Сборка
```bash
npm run build:prod # Production сборка в ./dist
```
## 📄 Страницы
- `/` - главная (в разработке)
- `/terms` - пользовательское соглашение
### Главная `/`
- **React** приложение с анимацией Lottie
- Динамическая страница "В разработке"
- Файлы: `index.html` + `index.js` (916 KB) + `index.css` (190 B)
## 🔧 Команды
### Terms `/terms`
- **Чистый HTML** без JavaScript
- Полный текст пользовательского соглашения
- SEO-оптимизирован
- Адаптивный дизайн
- Файлы: `terms.html` (10.5 KB) + `terms.css` (1.5 KB)
| Команда | Описание |
|---------|----------|
| `npm start` | Dev сервер |
| `npm run build` | Dev сборка |
| `npm run build:prod` | Production + SSG |
| `npm run build:prod:ssr` | Production + SSR |
## 🎨 Стилизация
## 📦 Результат сборки
```
dist/
├── index.html # Главная страница (SSG)
├── terms.html # Пользовательское соглашение (SEO)
├── index.js # React bundle
└── locales/ # i18n файлы
### CSS Modules (для React компонентов)
```typescript
import * as styles from './styles/main.module.scss';
element.className = styles.app;
```
---
**Преимущества:**
- Изоляция стилей
- Автоматические уникальные имена классов
- TypeScript поддержка
📚 Полная документация: **cloud.md**
### Обычный SCSS (для статических страниц)
```scss
// terms.scss
$mobile: 768px;
h1 {
font-size: 2.5rem;
@media (max-width: $mobile) {
font-size: 1.75rem; // Адаптивный шрифт
}
}
```
## 📱 Адаптивность
Все размеры шрифтов автоматически уменьшаются на мобильных устройствах (< 768px):
- H1: 2.5rem → 1.75rem
- H2: 1.75rem → 1.5rem
- H3: 1.25rem → 1.1rem
- Body: 1rem → 0.95rem
## 🗂️ Структура
```
src/
├── styles/
│ ├── main.module.scss # CSS Modules для React
│ ├── main.module.scss.d.ts # TypeScript типы
│ └── terms.scss # SCSS для статики
├── pages/
│ └── under-construction/ # Главная страница
├── index.tsx # Entry для React
├── terms.js # Entry для CSS
├── terms.html # Статический HTML
└── index.ejs # Шаблон для React
```
## 🌐 Деплой
После `npm run build:prod`:
- `index.html``brojs.ru/`
- `terms.html``brojs.ru/terms`
- Статика → `static.brojs.ru/landing/main/`
## 🔧 Технологии
### Core
- **React 18** - только для динамических частей
- **TypeScript** - типизация
- **Webpack 5** - сборка с multiple entry points
- **SCSS** - препроцессор
- **CSS Modules** - изоляция стилей
### Библиотеки
- **React Router DOM** - клиентский роутинг
- **i18next** - интернационализация
- **Lottie React** - анимации
### Dev зависимости
- **sass** + **sass-loader** - компиляция SCSS
- **css-loader** - обработка CSS (с поддержкой CSS Modules)
- **style-loader** - инжект CSS в dev режиме
- **mini-css-extract-plugin** - извлечение CSS в production
## 📚 Документация
Полная документация в [cloud.md](./cloud.md)
## 🤝 Участие
Проект использует минималистичный подход - добавляем зависимости только по мере необходимости!

View File

@ -4,11 +4,17 @@ import { i18nextReactInitConfig } from '@brojs/cli';
import { createRoot, hydrateRoot } from 'react-dom/client'
import App from './app';
import * as styles from './styles/main.module.scss';
i18next.t = i18next.t.bind(i18next);
const i18nextPromise = i18nextReactInitConfig(i18next);
const MOUNT_NODE = document.getElementById('app');
// Применяем класс к app контейнеру
if (MOUNT_NODE) {
MOUNT_NODE.className = styles.app || 'app';
}
(async () => {
await Promise.all([i18nextPromise]);

View File

@ -0,0 +1,13 @@
// Main styles for the landing page
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}
.app {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}

2
src/styles/main.module.scss.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export const app: string;

157
src/styles/terms.scss Normal file
View File

@ -0,0 +1,157 @@
// Terms page styles
$primary-color: #1a202c;
$bg-color: #f7fafc;
$white: #fff;
$text-secondary: #4a5568;
$text-muted: #718096;
$text-light: #a0aec0;
$border-color: #e2e8f0;
$link-color: #3182ce;
$container-max-width: 1200px;
$border-radius: 8px;
$spacing-base: 1rem;
// Breakpoints
$mobile: 768px;
$tablet: 1024px;
// Reset
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
line-height: 1.6;
color: $primary-color;
background: $bg-color;
}
.container {
max-width: $container-max-width;
margin: 0 auto;
padding: 40px 20px;
}
.terms-doc {
background: $white;
padding: 60px 80px;
border-radius: $border-radius;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
@media (max-width: 768px) {
padding: 40px 24px;
}
}
h1 {
font-size: 2.5rem;
font-weight: 700;
color: #2d3748;
text-align: center;
margin-bottom: 0.5rem;
@media (max-width: $mobile) {
font-size: 1.75rem;
}
}
.subtitle {
text-align: center;
color: $text-muted;
font-size: 1.1rem;
margin-bottom: $spacing-base;
@media (max-width: $mobile) {
font-size: 1rem;
}
}
.date {
text-align: center;
color: $text-light;
font-size: 0.9rem;
margin-bottom: 2rem;
}
hr {
border: none;
border-top: 1px solid $border-color;
margin: 2rem 0;
}
h2 {
font-size: 1.75rem;
font-weight: 700;
color: #2d3748;
margin: 2rem 0 $spacing-base;
@media (max-width: $mobile) {
font-size: 1.5rem;
margin: 1.5rem 0 0.75rem;
}
}
h3 {
font-size: 1.25rem;
font-weight: 600;
color: $text-secondary;
margin: 1.5rem 0 0.75rem;
@media (max-width: $mobile) {
font-size: 1.1rem;
margin: 1.25rem 0 0.5rem;
}
}
p {
margin-bottom: $spacing-base;
color: $text-secondary;
font-size: 1rem;
@media (max-width: $mobile) {
font-size: 0.95rem;
}
}
ul {
margin: $spacing-base 0 $spacing-base 2rem;
color: $text-secondary;
font-size: 1rem;
@media (max-width: $mobile) {
font-size: 0.95rem;
margin-left: 1.5rem;
}
li {
margin-bottom: 0.5rem;
}
}
a {
color: $link-color;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
strong {
font-weight: 600;
color: #2d3748;
}
.footer {
text-align: center;
margin-top: 3rem;
padding-top: 2rem;
border-top: 1px solid $border-color;
color: $text-light;
font-size: 0.9rem;
}

View File

@ -8,95 +8,7 @@
<meta name="description" content="Пользовательское соглашение для платформы обучения фронтенд-разработке BROJS.RU. Условия использования, обработка персональных данных, права и обязанности сторон.">
<meta name="yandex-verification" content="98f7e15d1ad66018">
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,600,700,900&subset=cyrillic,cyrillic-ext" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
line-height: 1.6;
color: #1a202c;
background: #f7fafc;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 40px 20px;
}
.terms-doc {
background: white;
padding: 60px 80px;
border-radius: 8px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
@media (max-width: 768px) {
.terms-doc { padding: 40px 24px; }
}
h1 {
font-size: 2.5rem;
font-weight: 700;
color: #2d3748;
text-align: center;
margin-bottom: 0.5rem;
}
.subtitle {
text-align: center;
color: #718096;
font-size: 1.1rem;
margin-bottom: 1rem;
}
.date {
text-align: center;
color: #a0aec0;
font-size: 0.9rem;
margin-bottom: 2rem;
}
hr {
border: none;
border-top: 1px solid #e2e8f0;
margin: 2rem 0;
}
h2 {
font-size: 1.75rem;
font-weight: 700;
color: #2d3748;
margin: 2rem 0 1rem;
}
h3 {
font-size: 1.25rem;
font-weight: 600;
color: #4a5568;
margin: 1.5rem 0 0.75rem;
}
p {
margin-bottom: 1rem;
color: #4a5568;
}
ul {
margin: 1rem 0 1rem 2rem;
color: #4a5568;
}
li {
margin-bottom: 0.5rem;
}
a {
color: #3182ce;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
strong {
font-weight: 600;
color: #2d3748;
}
.footer {
text-align: center;
margin-top: 3rem;
padding-top: 2rem;
border-top: 1px solid #e2e8f0;
color: #a0aec0;
font-size: 0.9rem;
}
</style>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cssPath %>">
</head>
<body>
<noscript><div><img src="https://mc.yandex.ru/watch/87860751" style="position:absolute; left:-9999px;" alt=""></div></noscript>

3
src/terms.js Normal file
View File

@ -0,0 +1,3 @@
// Entry point for terms page styles
import './styles/terms.scss';