diff --git a/cloud.md b/cloud.md index b6cd8aa..6cbd798 100644 --- a/cloud.md +++ b/cloud.md @@ -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 + +``` + ## Структура проекта ``` diff --git a/ijl.config.js b/ijl.config.js index 3243c0c..fc85bd5 100644 --- a/ijl.config.js +++ b/ijl.config.js @@ -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', }), diff --git a/package-lock.json b/package-lock.json index a7a05fe..cd24ccf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 620df5e..425e692 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/readme.md b/readme.md index 3057376..32ba23a 100644 --- a/readme.md +++ b/readme.md @@ -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) + +## 🤝 Участие + +Проект использует минималистичный подход - добавляем зависимости только по мере необходимости! diff --git a/src/index.tsx b/src/index.tsx index ceb3d34..c658798 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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]); diff --git a/src/styles/main.module.scss b/src/styles/main.module.scss new file mode 100644 index 0000000..542ba4a --- /dev/null +++ b/src/styles/main.module.scss @@ -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; +} diff --git a/src/styles/main.module.scss.d.ts b/src/styles/main.module.scss.d.ts new file mode 100644 index 0000000..a4bee34 --- /dev/null +++ b/src/styles/main.module.scss.d.ts @@ -0,0 +1,2 @@ +export const app: string; + diff --git a/src/styles/terms.scss b/src/styles/terms.scss new file mode 100644 index 0000000..048fda7 --- /dev/null +++ b/src/styles/terms.scss @@ -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; +} + diff --git a/src/terms.html b/src/terms.html index 700b238..cc06c51 100644 --- a/src/terms.html +++ b/src/terms.html @@ -8,95 +8,7 @@ - + diff --git a/src/terms.js b/src/terms.js new file mode 100644 index 0000000..8981cc7 --- /dev/null +++ b/src/terms.js @@ -0,0 +1,3 @@ +// Entry point for terms page styles +import './styles/terms.scss'; +