first commit
15
webpunk-templates/.editorconfig
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.toml]
|
||||||
|
indent_size = 4
|
||||||
46
webpunk-templates/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
.yarn/install-state.gz
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/ssl
|
||||||
|
/build
|
||||||
|
/dist
|
||||||
|
/dist-ssr
|
||||||
|
/temp
|
||||||
|
dist.zip
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
.env
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
6
webpunk-templates/.prettierignore
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Lockfiles
|
||||||
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|
||||||
|
# vendor
|
||||||
|
vendor/
|
||||||
14
webpunk-templates/.prettierrc.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "none",
|
||||||
|
"semi": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.css", "*.less", "*.sass", "*.scss"],
|
||||||
|
"options": {
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
264
webpunk-templates/.stylelint-order.json
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"order/order": ["custom-properties", "declarations"],
|
||||||
|
"order/properties-order": [
|
||||||
|
"content",
|
||||||
|
"counter-reset",
|
||||||
|
"counter-increment",
|
||||||
|
|
||||||
|
"all",
|
||||||
|
"position",
|
||||||
|
"top",
|
||||||
|
"right",
|
||||||
|
"bottom",
|
||||||
|
"left",
|
||||||
|
"inset",
|
||||||
|
"inset-block-start",
|
||||||
|
"inset-block-end",
|
||||||
|
"inset-inline-start",
|
||||||
|
"inset-inline-end",
|
||||||
|
"z-index",
|
||||||
|
|
||||||
|
"display",
|
||||||
|
"grid-template",
|
||||||
|
"grid-template-rows",
|
||||||
|
"grid-template-columns",
|
||||||
|
"grid-template-areas",
|
||||||
|
"grid-auto-rows",
|
||||||
|
"grid-auto-columns",
|
||||||
|
"grid-auto-flow",
|
||||||
|
"grid-area",
|
||||||
|
"grid-row",
|
||||||
|
"grid-column",
|
||||||
|
"grid-row-start",
|
||||||
|
"grid-row-end",
|
||||||
|
"grid-column-start",
|
||||||
|
"grid-column-end",
|
||||||
|
"flex",
|
||||||
|
"flex-grow",
|
||||||
|
"flex-shrink",
|
||||||
|
"flex-basis",
|
||||||
|
"flex-flow",
|
||||||
|
"flex-direction",
|
||||||
|
"flex-wrap",
|
||||||
|
"order",
|
||||||
|
"place-items",
|
||||||
|
"justify-content",
|
||||||
|
"justify-items",
|
||||||
|
"justify-self",
|
||||||
|
"align-content",
|
||||||
|
"align-items",
|
||||||
|
"align-self",
|
||||||
|
"grid-gap",
|
||||||
|
"gap",
|
||||||
|
"grid-row-gap",
|
||||||
|
"row-gap",
|
||||||
|
"grid-column-gap",
|
||||||
|
"column-gap",
|
||||||
|
"float",
|
||||||
|
"clear",
|
||||||
|
"box-sizing",
|
||||||
|
"writing-mode",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"max-width",
|
||||||
|
"max-height",
|
||||||
|
"min-width",
|
||||||
|
"min-height",
|
||||||
|
"aspect-ratio",
|
||||||
|
"inline-size",
|
||||||
|
"max-inline-size",
|
||||||
|
"min-inline-size",
|
||||||
|
"block-size",
|
||||||
|
"max-block-size",
|
||||||
|
"min-block-size",
|
||||||
|
"margin",
|
||||||
|
"margin-top",
|
||||||
|
"margin-right",
|
||||||
|
"margin-bottom",
|
||||||
|
"margin-left",
|
||||||
|
"padding",
|
||||||
|
"padding-top",
|
||||||
|
"padding-right",
|
||||||
|
"padding-bottom",
|
||||||
|
"padding-left",
|
||||||
|
"overflow",
|
||||||
|
"overflow-x",
|
||||||
|
"overflow-y",
|
||||||
|
|
||||||
|
"font",
|
||||||
|
"font-family",
|
||||||
|
"font-size",
|
||||||
|
"line-height",
|
||||||
|
"font-weight",
|
||||||
|
"font-style",
|
||||||
|
"text-align",
|
||||||
|
"text-align-last",
|
||||||
|
"vertical-align",
|
||||||
|
"color",
|
||||||
|
"font-display",
|
||||||
|
"font-variant",
|
||||||
|
"font-size-adjust",
|
||||||
|
"font-stretch",
|
||||||
|
"font-effect",
|
||||||
|
"font-emphasize",
|
||||||
|
"font-emphasize-position",
|
||||||
|
"font-emphasize-style",
|
||||||
|
"font-smooth",
|
||||||
|
"letter-spacing",
|
||||||
|
"white-space",
|
||||||
|
"text-transform",
|
||||||
|
"text-decoration",
|
||||||
|
"text-emphasis",
|
||||||
|
"text-emphasis-color",
|
||||||
|
"text-emphasis-style",
|
||||||
|
"text-emphasis-position",
|
||||||
|
"text-indent",
|
||||||
|
"text-justify",
|
||||||
|
"text-outline",
|
||||||
|
"text-wrap",
|
||||||
|
"text-overflow",
|
||||||
|
"text-overflow-ellipsis",
|
||||||
|
"text-overflow-mode",
|
||||||
|
"text-orientation",
|
||||||
|
"text-shadow",
|
||||||
|
"word-wrap",
|
||||||
|
"word-break",
|
||||||
|
"word-spacing",
|
||||||
|
"overflow-wrap",
|
||||||
|
"tab-size",
|
||||||
|
"hyphens",
|
||||||
|
"direction",
|
||||||
|
"unicode-bidi",
|
||||||
|
"columns",
|
||||||
|
"column-count",
|
||||||
|
"column-fill",
|
||||||
|
"column-rule",
|
||||||
|
"column-rule-color",
|
||||||
|
"column-rule-style",
|
||||||
|
"column-rule-width",
|
||||||
|
"column-span",
|
||||||
|
"column-width",
|
||||||
|
"src",
|
||||||
|
|
||||||
|
"page-break-after",
|
||||||
|
"page-break-before",
|
||||||
|
"page-break-inside",
|
||||||
|
"list-style",
|
||||||
|
"list-style-position",
|
||||||
|
"list-style-type",
|
||||||
|
"list-style-image",
|
||||||
|
"table-layout",
|
||||||
|
"empty-cells",
|
||||||
|
"caption-side",
|
||||||
|
"background",
|
||||||
|
"background-color",
|
||||||
|
"background-image",
|
||||||
|
"background-repeat",
|
||||||
|
"background-size",
|
||||||
|
"background-position",
|
||||||
|
"background-position-x",
|
||||||
|
"background-position-y",
|
||||||
|
"background-clip",
|
||||||
|
"background-origin",
|
||||||
|
"background-attachment",
|
||||||
|
"background-blend-mode",
|
||||||
|
"box-decoration-break",
|
||||||
|
"object-fit",
|
||||||
|
"border",
|
||||||
|
"border-width",
|
||||||
|
"border-style",
|
||||||
|
"border-color",
|
||||||
|
"border-top",
|
||||||
|
"border-block-start",
|
||||||
|
"border-top-width",
|
||||||
|
"border-top-style",
|
||||||
|
"border-top-color",
|
||||||
|
"border-right",
|
||||||
|
"border-inline-end",
|
||||||
|
"border-right-width",
|
||||||
|
"border-right-style",
|
||||||
|
"border-right-color",
|
||||||
|
"border-bottom",
|
||||||
|
"border-block-end",
|
||||||
|
"border-bottom-width",
|
||||||
|
"border-bottom-style",
|
||||||
|
"border-bottom-color",
|
||||||
|
"border-left",
|
||||||
|
"border-inline-start",
|
||||||
|
"border-left-width",
|
||||||
|
"border-left-style",
|
||||||
|
"border-left-color",
|
||||||
|
"border-radius",
|
||||||
|
"border-top-left-radius",
|
||||||
|
"border-top-right-radius",
|
||||||
|
"border-bottom-right-radius",
|
||||||
|
"border-bottom-left-radius",
|
||||||
|
"border-image",
|
||||||
|
"border-image-source",
|
||||||
|
"border-image-slice",
|
||||||
|
"border-image-width",
|
||||||
|
"border-image-outset",
|
||||||
|
"border-image-repeat",
|
||||||
|
"border-collapse",
|
||||||
|
"border-spacing",
|
||||||
|
"outline",
|
||||||
|
"outline-width",
|
||||||
|
"outline-style",
|
||||||
|
"outline-color",
|
||||||
|
"outline-offset",
|
||||||
|
"box-shadow",
|
||||||
|
"visibility",
|
||||||
|
"cursor",
|
||||||
|
"mix-blend-mode",
|
||||||
|
"backdrop-filter",
|
||||||
|
"will-change",
|
||||||
|
"transform",
|
||||||
|
"transform-origin",
|
||||||
|
"transform-style",
|
||||||
|
"translate",
|
||||||
|
"rotate",
|
||||||
|
"scale",
|
||||||
|
"backface-visibility",
|
||||||
|
"opacity",
|
||||||
|
"filter",
|
||||||
|
"perspective",
|
||||||
|
"perspective-origin",
|
||||||
|
|
||||||
|
"transition",
|
||||||
|
"transition-delay",
|
||||||
|
"transition-timing-function",
|
||||||
|
"transition-duration",
|
||||||
|
"transition-property",
|
||||||
|
"animation",
|
||||||
|
"animation-name",
|
||||||
|
"animation-duration",
|
||||||
|
"animation-play-state",
|
||||||
|
"animation-timing-function",
|
||||||
|
"animation-delay",
|
||||||
|
"animation-iteration-count",
|
||||||
|
"animation-direction",
|
||||||
|
"animation-fill-mode",
|
||||||
|
|
||||||
|
"appearance",
|
||||||
|
"clip",
|
||||||
|
"clip-path",
|
||||||
|
"resize",
|
||||||
|
"user-select",
|
||||||
|
"nav-index",
|
||||||
|
"nav-up",
|
||||||
|
"nav-right",
|
||||||
|
"nav-down",
|
||||||
|
"nav-left",
|
||||||
|
"pointer-events",
|
||||||
|
"quotes",
|
||||||
|
"touch-action",
|
||||||
|
"zoom",
|
||||||
|
"fill",
|
||||||
|
"fill-rule",
|
||||||
|
"clip-rule",
|
||||||
|
"stroke",
|
||||||
|
"stroke-width"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
1
webpunk-templates/.stylelintignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
vendor/
|
||||||
64
webpunk-templates/.stylelintrc.json
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"extends": ["stylelint-config-standard-scss", "./.stylelint-order.json"],
|
||||||
|
"plugins": ["stylelint-prettier", "stylelint-order"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["**/*.scss"],
|
||||||
|
"customSyntax": "postcss-scss"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"at-rule-empty-line-before": [
|
||||||
|
"always",
|
||||||
|
{
|
||||||
|
"except": ["blockless-after-blockless", "inside-block"],
|
||||||
|
"ignore": ["after-comment"],
|
||||||
|
"ignoreAtRules": ["else"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"at-rule-no-unknown": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"ignoreAtRules": [
|
||||||
|
"mixin",
|
||||||
|
"define-mixin",
|
||||||
|
"include",
|
||||||
|
"content",
|
||||||
|
"rules",
|
||||||
|
"extend",
|
||||||
|
"use",
|
||||||
|
"if",
|
||||||
|
"for"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"block-no-empty": true,
|
||||||
|
"color-hex-length": "long",
|
||||||
|
"declaration-no-important": true,
|
||||||
|
"declaration-empty-line-before": "never",
|
||||||
|
"declaration-block-no-redundant-longhand-properties": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"ignoreShorthands": ["columns", "grid-template", "flex-flow"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"font-family-name-quotes": "always-unless-keyword",
|
||||||
|
"no-irregular-whitespace": null,
|
||||||
|
"no-descending-specificity": null,
|
||||||
|
"rule-empty-line-before": "never",
|
||||||
|
"selector-class-pattern": "",
|
||||||
|
"shorthand-property-no-redundant-values": null,
|
||||||
|
"scss/dollar-variable-colon-space-after": "at-least-one-space",
|
||||||
|
"scss/dollar-variable-empty-line-before": null,
|
||||||
|
"scss/double-slash-comment-empty-line-before": [
|
||||||
|
"always",
|
||||||
|
{
|
||||||
|
"except": ["inside-block"],
|
||||||
|
"ignore": ["between-comments"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scss/no-global-function-names": null,
|
||||||
|
"scss/comment-no-empty": null,
|
||||||
|
"scss/at-import-partial-extension": null
|
||||||
|
}
|
||||||
|
}
|
||||||
19
webpunk-templates/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import globals from 'globals'
|
||||||
|
import pluginJs from '@eslint/js'
|
||||||
|
|
||||||
|
/** @type {import('eslint').Linter.Config[]} */
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"**/settings.js", "**/ssl-manager.js", "**/settings.json", "**/*.mjs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.js'],
|
||||||
|
languageOptions: { sourceType: 'script' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
languageOptions: { globals: globals.browser }
|
||||||
|
},
|
||||||
|
pluginJs.configs.recommended
|
||||||
|
]
|
||||||
856
webpunk-templates/gulpfile.mjs
Normal file
|
|
@ -0,0 +1,856 @@
|
||||||
|
import autoprefixer from 'autoprefixer'
|
||||||
|
import concat from 'gulp-concat'
|
||||||
|
import csso from 'gulp-csso'
|
||||||
|
import { deleteAsync } from 'del'
|
||||||
|
import gulp from 'gulp'
|
||||||
|
import gulpWebp from 'gulp-webp'
|
||||||
|
import imagemin, { mozjpeg, optipng, svgo } from 'gulp-imagemin'
|
||||||
|
import imageminAvif from 'imagemin-avif'
|
||||||
|
import order from 'gulp-order'
|
||||||
|
import plumber from 'gulp-plumber'
|
||||||
|
import postcss from 'gulp-postcss'
|
||||||
|
import pug from 'gulp-pug'
|
||||||
|
import rename from 'gulp-rename'
|
||||||
|
import uglify from 'gulp-uglify-es'
|
||||||
|
import * as dartSass from 'sass'
|
||||||
|
import gulpSass from 'gulp-sass'
|
||||||
|
import sourcemap from 'gulp-sourcemaps'
|
||||||
|
import svgstore from 'gulp-svgstore'
|
||||||
|
import sync from 'browser-sync'
|
||||||
|
import { nanoid } from 'nanoid'
|
||||||
|
import postcssUrl from 'postcss-url'
|
||||||
|
import inject from 'gulp-inject'
|
||||||
|
import newer from 'gulp-newer'
|
||||||
|
import twig from 'gulp-twig'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import { execSync } from 'child_process'
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = path.dirname(__filename)
|
||||||
|
|
||||||
|
// Загружаем переменные окружения из .env.local
|
||||||
|
const envPath = path.join(__dirname, '.env.local')
|
||||||
|
if (fs.existsSync(envPath)) {
|
||||||
|
dotenv.config({ path: envPath })
|
||||||
|
console.log('📝 Загружены настройки из .env.local')
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ Файл .env.local не найден. Запустите: gulp configInit')
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = '.'
|
||||||
|
const pathCss = '..'
|
||||||
|
const searchId = Date.now()
|
||||||
|
|
||||||
|
// Конфигурация для разных шаблонизаторов
|
||||||
|
const settings = {
|
||||||
|
templateEngine: process.env.TEMPLATE_ENGINE || 'pug', // 'pug' или 'twig'
|
||||||
|
dataPath: 'src/data', // путь к JSON файлам с данными
|
||||||
|
https: {
|
||||||
|
enabled: process.env.HTTPS === 'true',
|
||||||
|
keyPath: 'ssl/server.key',
|
||||||
|
certPath: 'ssl/server.crt'
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
|
||||||
|
host: process.env.HOST || 'localhost',
|
||||||
|
open: process.env.OPEN_BROWSER === 'true',
|
||||||
|
notify: process.env.BROWSER_SYNC_NOTIFY === 'true'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выводим текущие настройки при запуске
|
||||||
|
console.log('⚙️ Текущие настройки:')
|
||||||
|
console.log(` Шаблонизатор: ${settings.templateEngine}`)
|
||||||
|
console.log(` HTTPS: ${settings.https.enabled ? 'включен' : 'выключен'}`)
|
||||||
|
console.log(` Порт: ${settings.server.port}`)
|
||||||
|
console.log(` Хост: ${settings.server.host}`)
|
||||||
|
console.log(` Автооткрытие браузера: ${settings.server.open ? 'да' : 'нет'}`)
|
||||||
|
console.log(` Уведомления: ${settings.server.notify ? 'да' : 'нет'}`)
|
||||||
|
|
||||||
|
// Функция для загрузки данных из JSON файлов
|
||||||
|
const loadData = () => {
|
||||||
|
const dataPath = path.join(__dirname, settings.dataPath)
|
||||||
|
let data = {}
|
||||||
|
|
||||||
|
if (fs.existsSync(dataPath)) {
|
||||||
|
const files = fs.readdirSync(dataPath).filter(file => file.endsWith('.json'))
|
||||||
|
|
||||||
|
files.forEach(file => {
|
||||||
|
const fileName = path.basename(file, '.json')
|
||||||
|
const filePath = path.join(dataPath, file)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileContent = fs.readFileSync(filePath, 'utf8')
|
||||||
|
data[fileName] = JSON.parse(fileContent)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Ошибка загрузки данных из ${file}:`, error.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log(`📁 Папка с данными не найдена: ${dataPath}`)
|
||||||
|
console.log('💡 Запустите: gulp data для создания примера данных')
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
const getImageSrcset = (path, format, params = '') => {
|
||||||
|
return [1, 2]
|
||||||
|
.map((dpr) => `${baseUrl}${path}@${dpr}x.${format}${params} ${dpr}x`)
|
||||||
|
.join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getImageMime = (format) => {
|
||||||
|
switch (format) {
|
||||||
|
case 'avif':
|
||||||
|
return 'image/avif'
|
||||||
|
case 'webp':
|
||||||
|
return 'image/webp'
|
||||||
|
case 'jpeg':
|
||||||
|
case 'jpg':
|
||||||
|
return 'image/jpeg'
|
||||||
|
case 'png':
|
||||||
|
return 'image/png'
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sass = gulpSass(dartSass)
|
||||||
|
|
||||||
|
const clean = async () => {
|
||||||
|
return await deleteAsync(['dist'])
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = () => {
|
||||||
|
return gulp
|
||||||
|
.src(
|
||||||
|
[
|
||||||
|
'src/fonts/**/*.{woff,woff2}',
|
||||||
|
'src/img/**/*.{webm,webp,avif,jpg,jpeg,png,svg}',
|
||||||
|
'src/favicon/**/*',
|
||||||
|
'src/favicon.ico',
|
||||||
|
'src/settings.js',
|
||||||
|
'src/urls.json',
|
||||||
|
'src/robots.txt'
|
||||||
|
],
|
||||||
|
{ base: 'src', encoding: false }
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const css = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/scss/index.scss')
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(sourcemap.init())
|
||||||
|
.pipe(sass.sync().on('error', sass.logError))
|
||||||
|
.pipe(
|
||||||
|
postcss([
|
||||||
|
postcssUrl({
|
||||||
|
url: (asset) => {
|
||||||
|
if (asset.url.startsWith('/')) {
|
||||||
|
return `${pathCss}${asset.url}?v=${searchId}`
|
||||||
|
}
|
||||||
|
return asset.url
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
autoprefixer({ remove: false })
|
||||||
|
])
|
||||||
|
)
|
||||||
|
.pipe(rename('style.css'))
|
||||||
|
.pipe(sourcemap.write('.'))
|
||||||
|
.pipe(gulp.dest('dist/css'))
|
||||||
|
.pipe(sync.stream())
|
||||||
|
.pipe(csso())
|
||||||
|
.pipe(rename('style.min.css'))
|
||||||
|
.pipe(gulp.dest('dist/css'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const images = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/img/**/*.{png,jpg,jpeg,svg}', { base: 'src', encoding: false })
|
||||||
|
.pipe(newer('dist'))
|
||||||
|
.pipe(
|
||||||
|
imagemin(
|
||||||
|
[
|
||||||
|
optipng({ optimizationLevel: 5 }),
|
||||||
|
mozjpeg({ quality: 85, progressive: true }),
|
||||||
|
svgo({
|
||||||
|
plugins: [
|
||||||
|
{ name: 'removeViewBox', active: false },
|
||||||
|
{ name: 'removeXMLNS', active: false },
|
||||||
|
{ name: 'removeUnknownsAndDefaults', active: false }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
],
|
||||||
|
{ silent: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const webp = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/img/**/*.{png,jpg,jpeg}', { base: 'src', encoding: false })
|
||||||
|
.pipe(newer({ dest: 'dist', ext: '.webp' }))
|
||||||
|
.pipe(gulpWebp({ quality: 85 }))
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const avif = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/img/**/*.{png,jpg,jpeg}', { base: 'src', encoding: false })
|
||||||
|
.pipe(newer({ dest: 'dist', ext: '.avif' }))
|
||||||
|
.pipe(imagemin([imageminAvif({ quality: 60 })], { silent: true }))
|
||||||
|
.pipe(rename((path) => (path.extname = '.avif')))
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const processImages = gulp.parallel(images, webp, avif)
|
||||||
|
|
||||||
|
const sprite = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/icons/**/*.svg')
|
||||||
|
.pipe(svgstore({ inlineSvg: true }))
|
||||||
|
.pipe(rename('sprite.svg'))
|
||||||
|
.pipe(gulp.dest('dist/img'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const injectSprite = () => {
|
||||||
|
const spritePath = 'dist/img/sprite.svg'
|
||||||
|
|
||||||
|
// Проверяем, существует ли файл спрайта
|
||||||
|
if (!fs.existsSync(spritePath)) {
|
||||||
|
console.log('⚠️ Спрайт не найден, пропускаем инъекцию')
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
return gulp
|
||||||
|
.src('dist/*.html')
|
||||||
|
.pipe(
|
||||||
|
inject(gulp.src(spritePath), {
|
||||||
|
transform: (_, file) => {
|
||||||
|
return file.contents.toString()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инъекция спрайта только в конкретный файл
|
||||||
|
const injectSpriteToFile = (htmlFile) => {
|
||||||
|
const spritePath = 'dist/img/sprite.svg'
|
||||||
|
|
||||||
|
if (!fs.existsSync(spritePath) || !fs.existsSync(htmlFile)) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
return gulp
|
||||||
|
.src(htmlFile)
|
||||||
|
.pipe(
|
||||||
|
inject(gulp.src(spritePath), {
|
||||||
|
transform: (_, file) => {
|
||||||
|
return file.contents.toString()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsCommon = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/js/common/*.js')
|
||||||
|
.pipe(
|
||||||
|
plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error('JS Common Error:', err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(order(['_utils.js', '*.js']))
|
||||||
|
.pipe(sourcemap.init())
|
||||||
|
.pipe(concat('script.js'))
|
||||||
|
.pipe(sourcemap.write('.'))
|
||||||
|
.pipe(gulp.dest('dist/js'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsCommonMin = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/js/common/*.js')
|
||||||
|
.pipe(
|
||||||
|
plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error('JS Common Min Error:', err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(order(['_utils.js', '*.js']))
|
||||||
|
.pipe(concat('script.min.js'))
|
||||||
|
.pipe(uglify.default())
|
||||||
|
.pipe(gulp.dest('dist/js'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsVendor = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/js/vendor/*.js')
|
||||||
|
.pipe(
|
||||||
|
plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error('JS Vendor Error:', err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(concat('vendor.js'))
|
||||||
|
.pipe(gulp.dest('dist/js'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsVendorMin = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/js/vendor/*.js')
|
||||||
|
.pipe(
|
||||||
|
plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error('JS Vendor Min Error:', err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(concat('vendor.min.js'))
|
||||||
|
.pipe(uglify.default())
|
||||||
|
.pipe(gulp.dest('dist/js'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const js = gulp.parallel(jsCommon, jsCommonMin, jsVendor, jsVendorMin)
|
||||||
|
|
||||||
|
// Простая обработка Pug шаблонов
|
||||||
|
const htmlPug = () => {
|
||||||
|
const data = loadData()
|
||||||
|
|
||||||
|
return gulp
|
||||||
|
.src('src/pug/pages/**/*.pug')
|
||||||
|
.pipe(plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error('Pug Error:', err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.pipe(
|
||||||
|
pug({
|
||||||
|
pretty: true,
|
||||||
|
basedir: 'src/pug',
|
||||||
|
locals: {
|
||||||
|
baseUrl,
|
||||||
|
searchId,
|
||||||
|
getSrcset: getImageSrcset,
|
||||||
|
getMime: getImageMime,
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Простая обработка Twig шаблонов
|
||||||
|
const htmlTwig = () => {
|
||||||
|
const data = loadData()
|
||||||
|
|
||||||
|
return gulp
|
||||||
|
.src('src/twig/pages/**/*.twig')
|
||||||
|
.pipe(plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error('Twig Error:', err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.pipe(
|
||||||
|
twig({
|
||||||
|
base: 'src/twig',
|
||||||
|
data: {
|
||||||
|
baseUrl,
|
||||||
|
searchId,
|
||||||
|
getSrcset: getImageSrcset,
|
||||||
|
getMime: getImageMime,
|
||||||
|
...data
|
||||||
|
},
|
||||||
|
functions: [
|
||||||
|
{
|
||||||
|
name: 'getSrcset',
|
||||||
|
func: getImageSrcset
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'getMime',
|
||||||
|
func: getImageMime
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pug с инкрементальной сборкой
|
||||||
|
const htmlPugIncremental = () => {
|
||||||
|
const data = loadData()
|
||||||
|
|
||||||
|
return gulp
|
||||||
|
.src('src/pug/pages/**/*.pug')
|
||||||
|
.pipe(plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error('Pug Error:', err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.pipe(newer({
|
||||||
|
dest: 'dist',
|
||||||
|
ext: '.html'
|
||||||
|
}))
|
||||||
|
.pipe(
|
||||||
|
pug({
|
||||||
|
pretty: true,
|
||||||
|
basedir: 'src/pug',
|
||||||
|
locals: {
|
||||||
|
baseUrl,
|
||||||
|
searchId,
|
||||||
|
getSrcset: getImageSrcset,
|
||||||
|
getMime: getImageMime,
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
.on('end', () => {
|
||||||
|
console.log('📄 Обработаны только измененные Pug файлы')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Twig с инкрементальной сборкой
|
||||||
|
const htmlTwigIncremental = () => {
|
||||||
|
const data = loadData()
|
||||||
|
|
||||||
|
return gulp
|
||||||
|
.src('src/twig/pages/**/*.twig')
|
||||||
|
.pipe(plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error('Twig Error:', err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.pipe(newer({
|
||||||
|
dest: 'dist',
|
||||||
|
ext: '.html'
|
||||||
|
}))
|
||||||
|
.pipe(
|
||||||
|
twig({
|
||||||
|
base: 'src/twig',
|
||||||
|
data: {
|
||||||
|
baseUrl,
|
||||||
|
searchId,
|
||||||
|
getSrcset: getImageSrcset,
|
||||||
|
getMime: getImageMime,
|
||||||
|
...data
|
||||||
|
},
|
||||||
|
functions: [
|
||||||
|
{
|
||||||
|
name: 'getSrcset',
|
||||||
|
func: getImageSrcset
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'getMime',
|
||||||
|
func: getImageMime
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
.on('end', () => {
|
||||||
|
console.log('📄 Обработаны только измененные Twig файлы')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Универсальная функция для обработки HTML
|
||||||
|
const html = (incremental = false) => {
|
||||||
|
if (settings.templateEngine === 'twig') {
|
||||||
|
return incremental ? htmlTwigIncremental() : htmlTwig()
|
||||||
|
} else {
|
||||||
|
return incremental ? htmlPugIncremental() : htmlPug()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создание SSL сертификатов для разработки
|
||||||
|
const createSSLCerts = (done) => {
|
||||||
|
const sslDir = path.join(__dirname, 'ssl')
|
||||||
|
const keyPath = path.join(sslDir, 'server.key')
|
||||||
|
const certPath = path.join(sslDir, 'server.crt')
|
||||||
|
|
||||||
|
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
|
||||||
|
console.log('✅ SSL сертификаты уже существуют')
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(sslDir)) {
|
||||||
|
fs.mkdirSync(sslDir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('🔐 Создание SSL сертификатов для разработки...')
|
||||||
|
|
||||||
|
execSync(`openssl genrsa -out ${keyPath} 2048`, { stdio: 'pipe' })
|
||||||
|
|
||||||
|
const certCommand = `openssl req -new -x509 -key ${keyPath} -out ${certPath} -days 365 -subj "/C=RU/ST=Local/L=Local/O=Development/OU=Dev/CN=localhost"`
|
||||||
|
execSync(certCommand, { stdio: 'pipe' })
|
||||||
|
|
||||||
|
console.log('✅ SSL сертификаты успешно созданы')
|
||||||
|
console.log('⚠️ Это сертификаты только для разработки!')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка создания SSL сертификатов:')
|
||||||
|
console.error('Убедитесь что OpenSSL установлен в системе')
|
||||||
|
}
|
||||||
|
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создание конфигурационного файла
|
||||||
|
const createHTTPSConfig = (done) => {
|
||||||
|
const configPath = path.join(__dirname, '.env.local')
|
||||||
|
|
||||||
|
if (!fs.existsSync(configPath)) {
|
||||||
|
const envContent = `# Конфигурация для разработки
|
||||||
|
# Этот файл автоматически загружается при запуске gulp
|
||||||
|
|
||||||
|
# ===== ОСНОВНЫЕ НАСТРОЙКИ =====
|
||||||
|
|
||||||
|
# Включить HTTPS (true/false)
|
||||||
|
# После изменения нужно перезапустить gulp
|
||||||
|
HTTPS=false
|
||||||
|
|
||||||
|
# Шаблонизатор (pug/twig)
|
||||||
|
# После изменения нужно перезапустить gulp
|
||||||
|
TEMPLATE_ENGINE=pug
|
||||||
|
|
||||||
|
# ===== НАСТРОЙКИ СЕРВЕРА =====
|
||||||
|
|
||||||
|
# Порт для разработки
|
||||||
|
PORT=3000
|
||||||
|
|
||||||
|
# Хост для разработки (localhost, 127.0.0.1, 0.0.0.0 для доступа извне)
|
||||||
|
HOST=localhost
|
||||||
|
|
||||||
|
# Автоматически открывать браузер при запуске (true/false)
|
||||||
|
OPEN_BROWSER=false
|
||||||
|
|
||||||
|
# Показывать уведомления BrowserSync (true/false)
|
||||||
|
BROWSER_SYNC_NOTIFY=false
|
||||||
|
`
|
||||||
|
|
||||||
|
fs.writeFileSync(configPath, envContent)
|
||||||
|
console.log('✅ Создан файл конфигурации: .env.local')
|
||||||
|
console.log('📝 Теперь вы можете настроить параметры в .env.local')
|
||||||
|
} else {
|
||||||
|
console.log('ℹ️ Файл .env.local уже существует')
|
||||||
|
}
|
||||||
|
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для перезагрузки конфигурации
|
||||||
|
const reloadConfig = (done) => {
|
||||||
|
const envPath = path.join(__dirname, '.env.local')
|
||||||
|
if (fs.existsSync(envPath)) {
|
||||||
|
// Очищаем старые значения
|
||||||
|
delete process.env.TEMPLATE_ENGINE
|
||||||
|
delete process.env.HTTPS
|
||||||
|
delete process.env.PORT
|
||||||
|
delete process.env.HOST
|
||||||
|
delete process.env.OPEN_BROWSER
|
||||||
|
delete process.env.BROWSER_SYNC_NOTIFY
|
||||||
|
|
||||||
|
// Загружаем новые
|
||||||
|
dotenv.config({ path: envPath, override: true })
|
||||||
|
|
||||||
|
// Обновляем настройки
|
||||||
|
settings.templateEngine = process.env.TEMPLATE_ENGINE || 'pug'
|
||||||
|
settings.https.enabled = process.env.HTTPS === 'true'
|
||||||
|
settings.server.port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000
|
||||||
|
settings.server.host = process.env.HOST || 'localhost'
|
||||||
|
settings.server.open = process.env.OPEN_BROWSER === 'true'
|
||||||
|
settings.server.notify = process.env.BROWSER_SYNC_NOTIFY === 'true'
|
||||||
|
|
||||||
|
console.log('🔄 Конфигурация перезагружена!')
|
||||||
|
console.log('⚙️ Новые настройки:')
|
||||||
|
console.log(` Шаблонизатор: ${settings.templateEngine}`)
|
||||||
|
console.log(` HTTPS: ${settings.https.enabled ? 'включен' : 'выключен'}`)
|
||||||
|
console.log(` Порт: ${settings.server.port}`)
|
||||||
|
console.log(` Хост: ${settings.server.host}`)
|
||||||
|
console.log('⚠️ Для применения HTTPS и смены шаблонизатора нужен перезапуск!')
|
||||||
|
} else {
|
||||||
|
console.log('❌ Файл .env.local не найден')
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Задача для создания примера структуры данных
|
||||||
|
const createDataExample = (done) => {
|
||||||
|
const dataDir = path.join(__dirname, 'src/data')
|
||||||
|
|
||||||
|
if (!fs.existsSync(dataDir)) {
|
||||||
|
fs.mkdirSync(dataDir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const exampleData = {
|
||||||
|
site: {
|
||||||
|
title: 'Мой сайт',
|
||||||
|
description: 'Описание сайта',
|
||||||
|
author: 'Автор'
|
||||||
|
},
|
||||||
|
navigation: [
|
||||||
|
{ title: 'Главная', url: '/' },
|
||||||
|
{ title: 'О нас', url: '/about' },
|
||||||
|
{ title: 'Контакты', url: '/contacts' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(dataDir, 'global.json'),
|
||||||
|
JSON.stringify(exampleData, null, 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('✅ Создан пример файла данных: src/data/global.json')
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
const refresh = (done) => {
|
||||||
|
sync.reload()
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка конкретного Pug файла
|
||||||
|
const processSinglePugFile = (filePath) => {
|
||||||
|
const data = loadData()
|
||||||
|
const relativePath = path.relative('src/pug/pages', filePath)
|
||||||
|
const outputPath = path.join('dist', relativePath.replace('.pug', '.html'))
|
||||||
|
|
||||||
|
console.log(`📄 Обработка файла: ${relativePath}`)
|
||||||
|
|
||||||
|
return gulp
|
||||||
|
.src(filePath)
|
||||||
|
.pipe(plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error('Pug Error:', err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.pipe(
|
||||||
|
pug({
|
||||||
|
pretty: true,
|
||||||
|
basedir: 'src/pug',
|
||||||
|
locals: {
|
||||||
|
baseUrl,
|
||||||
|
searchId,
|
||||||
|
getSrcset: getImageSrcset,
|
||||||
|
getMime: getImageMime,
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(rename(path.basename(outputPath)))
|
||||||
|
.pipe(gulp.dest(path.dirname(outputPath)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка конкретного Twig файла
|
||||||
|
const processSingleTwigFile = (filePath) => {
|
||||||
|
const data = loadData()
|
||||||
|
const relativePath = path.relative('src/twig/pages', filePath)
|
||||||
|
const outputPath = path.join('dist', relativePath.replace('.twig', '.html'))
|
||||||
|
|
||||||
|
console.log(`📄 Обработка файла: ${relativePath}`)
|
||||||
|
|
||||||
|
return gulp
|
||||||
|
.src(filePath)
|
||||||
|
.pipe(plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error('Twig Error:', err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.pipe(
|
||||||
|
twig({
|
||||||
|
base: 'src/twig',
|
||||||
|
data: {
|
||||||
|
baseUrl,
|
||||||
|
searchId,
|
||||||
|
getSrcset: getImageSrcset,
|
||||||
|
getMime: getImageMime,
|
||||||
|
...data
|
||||||
|
},
|
||||||
|
functions: [
|
||||||
|
{
|
||||||
|
name: 'getSrcset',
|
||||||
|
func: getImageSrcset
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'getMime',
|
||||||
|
func: getImageMime
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(rename(path.basename(outputPath)))
|
||||||
|
.pipe(gulp.dest(path.dirname(outputPath)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сервер с поддержкой HTTPS
|
||||||
|
const server = () => {
|
||||||
|
let serverConfig = {
|
||||||
|
server: 'dist/',
|
||||||
|
notify: settings.server.notify,
|
||||||
|
open: settings.server.open,
|
||||||
|
cors: true,
|
||||||
|
ui: false,
|
||||||
|
port: settings.server.port,
|
||||||
|
host: settings.server.host
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.https.enabled) {
|
||||||
|
const keyPath = path.join(__dirname, settings.https.keyPath)
|
||||||
|
const certPath = path.join(__dirname, settings.https.certPath)
|
||||||
|
|
||||||
|
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
|
||||||
|
serverConfig.https = {
|
||||||
|
key: keyPath,
|
||||||
|
cert: certPath
|
||||||
|
}
|
||||||
|
console.log('🔒 Запуск сервера с HTTPS')
|
||||||
|
console.log(`📍 https://${serverConfig.host}:${serverConfig.port}`)
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ HTTPS включен, но сертификаты не найдены')
|
||||||
|
console.log('Запустите: gulp ssl для создания сертификатов')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('🌐 Запуск HTTP сервера')
|
||||||
|
console.log(`📍 http://${serverConfig.host}:${serverConfig.port}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
sync.init(serverConfig)
|
||||||
|
|
||||||
|
// Умное наблюдение за страницами
|
||||||
|
if (settings.templateEngine === 'twig') {
|
||||||
|
// Отслеживание изменений в страницах Twig
|
||||||
|
const twigWatcher = gulp.watch('src/twig/pages/**/*.twig')
|
||||||
|
twigWatcher.on('change', (filePath) => {
|
||||||
|
console.log(`🔄 Изменен файл: ${path.relative(__dirname, filePath)}`)
|
||||||
|
return gulp.series(
|
||||||
|
() => processSingleTwigFile(filePath),
|
||||||
|
refresh
|
||||||
|
)()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Наблюдение за общими файлами - полная пересборка
|
||||||
|
gulp.watch(['src/twig/**/*.twig', '!src/twig/pages/**/*.twig'], gulp.series(() => html(false), injectSprite, refresh))
|
||||||
|
} else {
|
||||||
|
// Отслеживание изменений в страницах Pug
|
||||||
|
const pugWatcher = gulp.watch('src/pug/pages/**/*.pug')
|
||||||
|
pugWatcher.on('change', (filePath) => {
|
||||||
|
console.log(`🔄 Изменен файл: ${path.relative(__dirname, filePath)}`)
|
||||||
|
return gulp.series(
|
||||||
|
() => processSinglePugFile(filePath),
|
||||||
|
refresh
|
||||||
|
)()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Наблюдение за общими файлами - полная пересборка
|
||||||
|
gulp.watch(['src/pug/**/*.pug', '!src/pug/pages/**/*.pug'], gulp.series(() => html(false), injectSprite, refresh))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Наблюдение за данными - полная пересборка
|
||||||
|
gulp.watch('src/data/**/*.json', gulp.series(() => html(false), injectSprite, refresh))
|
||||||
|
|
||||||
|
// Наблюдение за спрайтами - полная пересборка с инъекцией
|
||||||
|
gulp.watch('src/icons/**/*.svg', gulp.series(sprite, () => html(false), injectSprite, refresh))
|
||||||
|
|
||||||
|
// Остальные файлы
|
||||||
|
gulp.watch('src/scss/**/*.scss', gulp.series(css))
|
||||||
|
gulp.watch('src/js/**/*.js', gulp.series(js, refresh))
|
||||||
|
gulp.watch('src/img/**/*.{png,jpg,jpeg,svg}', gulp.series(processImages, refresh))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Экспорт задач
|
||||||
|
export const build = gulp.series(
|
||||||
|
clean,
|
||||||
|
copy,
|
||||||
|
css,
|
||||||
|
js,
|
||||||
|
processImages,
|
||||||
|
sprite,
|
||||||
|
() => html(false),
|
||||||
|
injectSprite
|
||||||
|
)
|
||||||
|
|
||||||
|
export const dev = gulp.series(build, server)
|
||||||
|
export const data = createDataExample
|
||||||
|
export const ssl = createSSLCerts
|
||||||
|
export const configInit = createHTTPSConfig
|
||||||
|
export const configReload = reloadConfig
|
||||||
|
|
||||||
|
// HTTPS команды
|
||||||
|
export const devSSL = gulp.series(
|
||||||
|
(done) => {
|
||||||
|
settings.https.enabled = true
|
||||||
|
done()
|
||||||
|
},
|
||||||
|
ssl,
|
||||||
|
build,
|
||||||
|
server
|
||||||
|
)
|
||||||
|
|
||||||
|
// Команды с выбором шаблонизатора
|
||||||
|
export const buildPug = gulp.series(
|
||||||
|
(done) => { settings.templateEngine = 'pug'; done() },
|
||||||
|
build
|
||||||
|
)
|
||||||
|
|
||||||
|
export const buildTwig = gulp.series(
|
||||||
|
(done) => { settings.templateEngine = 'twig'; done() },
|
||||||
|
build
|
||||||
|
)
|
||||||
|
|
||||||
|
export const devPug = gulp.series(
|
||||||
|
(done) => { settings.templateEngine = 'pug'; done() },
|
||||||
|
build,
|
||||||
|
server
|
||||||
|
)
|
||||||
|
|
||||||
|
export const devTwig = gulp.series(
|
||||||
|
(done) => { settings.templateEngine = 'twig'; done() },
|
||||||
|
build,
|
||||||
|
server
|
||||||
|
)
|
||||||
|
|
||||||
|
export const devPugSSL = gulp.series(
|
||||||
|
(done) => {
|
||||||
|
settings.templateEngine = 'pug'
|
||||||
|
settings.https.enabled = true
|
||||||
|
done()
|
||||||
|
},
|
||||||
|
ssl,
|
||||||
|
build,
|
||||||
|
server
|
||||||
|
)
|
||||||
|
|
||||||
|
export const devTwigSSL = gulp.series(
|
||||||
|
(done) => {
|
||||||
|
settings.templateEngine = 'twig'
|
||||||
|
settings.https.enabled = true
|
||||||
|
done()
|
||||||
|
},
|
||||||
|
ssl,
|
||||||
|
build,
|
||||||
|
server
|
||||||
|
)
|
||||||
257
webpunk-templates/gulpfile.old.mjs
Normal file
|
|
@ -0,0 +1,257 @@
|
||||||
|
import autoprefixer from 'autoprefixer'
|
||||||
|
import concat from 'gulp-concat'
|
||||||
|
import csso from 'gulp-csso'
|
||||||
|
import { deleteAsync } from 'del'
|
||||||
|
import gulp from 'gulp'
|
||||||
|
import gulpWebp from 'gulp-webp'
|
||||||
|
import imagemin, { mozjpeg, optipng, svgo } from 'gulp-imagemin'
|
||||||
|
import imageminAvif from 'imagemin-avif'
|
||||||
|
import order from 'gulp-order'
|
||||||
|
import plumber from 'gulp-plumber'
|
||||||
|
import postcss from 'gulp-postcss'
|
||||||
|
import pug from 'gulp-pug'
|
||||||
|
import rename from 'gulp-rename'
|
||||||
|
import uglify from 'gulp-uglify-es'
|
||||||
|
import * as dartSass from 'sass'
|
||||||
|
import gulpSass from 'gulp-sass'
|
||||||
|
import sourcemap from 'gulp-sourcemaps'
|
||||||
|
import svgstore from 'gulp-svgstore'
|
||||||
|
import sync from 'browser-sync'
|
||||||
|
import { nanoid } from 'nanoid'
|
||||||
|
import postcssUrl from 'postcss-url'
|
||||||
|
import inject from 'gulp-inject'
|
||||||
|
import newer from 'gulp-newer' // Added gulp-newer
|
||||||
|
|
||||||
|
const baseUrl = '.'
|
||||||
|
const pathCss = '..'
|
||||||
|
const searchId = Date.now()
|
||||||
|
|
||||||
|
const getImageSrcset = (path, format, params) => {
|
||||||
|
return [1, 2]
|
||||||
|
.map((dpr) => `${baseUrl}${path}@${dpr}x.${format}${params} ${dpr}x`)
|
||||||
|
.join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getImageMime = (format) => {
|
||||||
|
switch (format) {
|
||||||
|
case 'avif':
|
||||||
|
return 'image/avif'
|
||||||
|
case 'webp':
|
||||||
|
return 'image/webp'
|
||||||
|
case 'jpeg':
|
||||||
|
case 'jpg':
|
||||||
|
return 'image/jpeg'
|
||||||
|
case 'png':
|
||||||
|
return 'image/png'
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sass = gulpSass(dartSass)
|
||||||
|
|
||||||
|
const clean = async () => {
|
||||||
|
return await deleteAsync(['dist'])
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = () => {
|
||||||
|
return gulp
|
||||||
|
.src(
|
||||||
|
[
|
||||||
|
'src/fonts/**/*.{woff,woff2}',
|
||||||
|
'src/img/**/*.{webm,webp,avif,jpg,jpeg,png,svg}',
|
||||||
|
'src/favicon/**/*',
|
||||||
|
'src/favicon.ico',
|
||||||
|
'src/settings.js',
|
||||||
|
'src/urls.json',
|
||||||
|
'src/robots.txt'
|
||||||
|
],
|
||||||
|
{ base: 'src', encoding: false }
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const css = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/scss/index.scss')
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(sourcemap.init())
|
||||||
|
.pipe(sass.sync().on('error', sass.logError))
|
||||||
|
.pipe(
|
||||||
|
postcss([
|
||||||
|
postcssUrl({
|
||||||
|
url: (asset) => {
|
||||||
|
if (asset.url.startsWith('/')) {
|
||||||
|
return `${pathCss}${asset.url}?v=${searchId}`
|
||||||
|
}
|
||||||
|
return asset.url
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
autoprefixer({ remove: false })
|
||||||
|
])
|
||||||
|
)
|
||||||
|
.pipe(rename('style.css'))
|
||||||
|
.pipe(sourcemap.write('.'))
|
||||||
|
.pipe(gulp.dest('dist/css'))
|
||||||
|
.pipe(sync.stream())
|
||||||
|
.pipe(csso())
|
||||||
|
.pipe(rename('style.min.css'))
|
||||||
|
.pipe(gulp.dest('dist/css'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const images = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/img/**/*.{png,jpg,jpeg,svg}', { base: 'src', encoding: false }) // Process all images in subfolders, keep base for dest path
|
||||||
|
.pipe(newer('dist')) // Process only newer files, compare with base 'dist' (e.g. dist/img/...)
|
||||||
|
.pipe(
|
||||||
|
imagemin(
|
||||||
|
[
|
||||||
|
optipng({ optimizationLevel: 3 }),
|
||||||
|
mozjpeg({ quality: 80, progressive: true }), // Quality for JPEG
|
||||||
|
svgo({
|
||||||
|
plugins: [{ name: 'removeUnknownsAndDefaults', active: false }]
|
||||||
|
})
|
||||||
|
],
|
||||||
|
{ silent: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist')) // Output to dist, maintaining folder structure from base
|
||||||
|
}
|
||||||
|
|
||||||
|
const webp = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/img/**/*.{png,jpg,jpeg}', { base: 'src', encoding: false }) // Process all images in subfolders
|
||||||
|
.pipe(newer({ dest: 'dist', ext: '.webp' })) // Process only newer, check against .webp extension in dist
|
||||||
|
.pipe(gulpWebp({ quality: 80 })) // Quality for WebP
|
||||||
|
.pipe(gulp.dest('dist')) // Output to dist, maintaining folder structure
|
||||||
|
}
|
||||||
|
|
||||||
|
const avif = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/img/**/*.{png,jpg,jpeg}', { base: 'src', encoding: false }) // Process all images in subfolders
|
||||||
|
.pipe(newer({ dest: 'dist', ext: '.avif' })) // Process only newer, check against .avif extension in dist
|
||||||
|
.pipe(imagemin([imageminAvif({ quality: 50 })], { silent: true })) // Quality for AVIF
|
||||||
|
.pipe(rename((path) => (path.extname = '.avif')))
|
||||||
|
.pipe(gulp.dest('dist')) // Output to dist, maintaining folder structure
|
||||||
|
}
|
||||||
|
|
||||||
|
const processImages = gulp.parallel(images, webp, avif)
|
||||||
|
|
||||||
|
const sprite = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/icons/**/*.svg')
|
||||||
|
.pipe(svgstore({ inlineSvg: true }))
|
||||||
|
.pipe(rename(`sprite.svg`))
|
||||||
|
.pipe(gulp.dest('dist/img'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const injectSprite = () => {
|
||||||
|
const spritePath = 'dist/img/sprite.svg'
|
||||||
|
|
||||||
|
return gulp
|
||||||
|
.src('dist/*.html')
|
||||||
|
.pipe(
|
||||||
|
inject(gulp.src(spritePath), {
|
||||||
|
transform: (_, file) => {
|
||||||
|
return file.contents.toString()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsCommon = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/js/common/*.js')
|
||||||
|
.pipe(
|
||||||
|
plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error(err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(order(['_utils.js', '*.js']))
|
||||||
|
.pipe(concat(`script.js`))
|
||||||
|
.pipe(gulp.dest('dist/js'))
|
||||||
|
.pipe(uglify.default())
|
||||||
|
.pipe(rename(`script.min.js`))
|
||||||
|
.pipe(gulp.dest('dist/js'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsVendor = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/js/vendor/*.js')
|
||||||
|
.pipe(
|
||||||
|
plumber({
|
||||||
|
errorHandler(err) {
|
||||||
|
console.error(err.toString())
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(concat(`vendor.js`))
|
||||||
|
.pipe(gulp.dest('dist/js'))
|
||||||
|
.pipe(uglify.default())
|
||||||
|
.pipe(rename(`script.min.js`))
|
||||||
|
.pipe(gulp.dest('dist/js'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = () => {
|
||||||
|
return gulp
|
||||||
|
.src('src/pug/pages/**/*.pug')
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(
|
||||||
|
pug({
|
||||||
|
pretty: true,
|
||||||
|
basedir: 'src/pug',
|
||||||
|
locals: {
|
||||||
|
baseUrl,
|
||||||
|
searchId,
|
||||||
|
getSrcset: getImageSrcset,
|
||||||
|
getMime: getImageMime
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const refresh = (done) => {
|
||||||
|
sync.reload()
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = () => {
|
||||||
|
sync.init({
|
||||||
|
server: 'dist/',
|
||||||
|
notify: false,
|
||||||
|
open: false,
|
||||||
|
cors: true,
|
||||||
|
ui: false
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.watch('src/pug/**/*.{pug,js}', gulp.series(html, injectSprite, refresh))
|
||||||
|
gulp.watch(
|
||||||
|
'src/icons/**/*.svg',
|
||||||
|
gulp.series(sprite, html, injectSprite, refresh)
|
||||||
|
)
|
||||||
|
gulp.watch('src/scss/**/*.scss', gulp.series(css))
|
||||||
|
gulp.watch('src/js/**/*.js', gulp.series(jsVendor, jsCommon, refresh))
|
||||||
|
// Watch for source image changes and run all image processing tasks.
|
||||||
|
// gulp-newer within each task will ensure only necessary files are processed.
|
||||||
|
gulp.watch('src/img/**/*.{png,jpg,jpeg,svg}', gulp.series(processImages, refresh))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const build = gulp.series(
|
||||||
|
clean,
|
||||||
|
copy,
|
||||||
|
css,
|
||||||
|
jsVendor,
|
||||||
|
jsCommon,
|
||||||
|
processImages,
|
||||||
|
sprite,
|
||||||
|
html,
|
||||||
|
injectSprite
|
||||||
|
)
|
||||||
|
|
||||||
|
export const dev = gulp.series(build, server)
|
||||||
21
webpunk-templates/netlify.toml
Executable file
|
|
@ -0,0 +1,21 @@
|
||||||
|
[[redirects]]
|
||||||
|
from = "/*"
|
||||||
|
to = "/index.html"
|
||||||
|
status = 200
|
||||||
|
|
||||||
|
[[headers]]
|
||||||
|
for = "/*"
|
||||||
|
[headers.values]
|
||||||
|
cache-control = '''
|
||||||
|
max-age=0,
|
||||||
|
no-cache,
|
||||||
|
no-store,
|
||||||
|
must-revalidate'''
|
||||||
|
|
||||||
|
X-Robots-Tag = 'noindex'
|
||||||
|
|
||||||
|
[build]
|
||||||
|
command = "pnpm run build"
|
||||||
|
|
||||||
|
[build.environment]
|
||||||
|
NODE_VERSION = "20.18.2"
|
||||||
79
webpunk-templates/package.json
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"name": "webpunk-templates",
|
||||||
|
"type":"module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "gulp dev",
|
||||||
|
"dev:pug": "gulp devPug",
|
||||||
|
"dev:twig": "gulp devTwig",
|
||||||
|
"dev:https": "gulp devSSL",
|
||||||
|
"dev:pug:https": "gulp devPugSSL",
|
||||||
|
"dev:twig:https": "gulp devTwigSSL",
|
||||||
|
"build": "gulp build",
|
||||||
|
"build:pug": "gulp buildPug",
|
||||||
|
"build:twig": "gulp buildTwig",
|
||||||
|
"ssl:create": "gulp ssl",
|
||||||
|
"ssl:check": "node ssl-manager.js check",
|
||||||
|
"ssl:info": "node ssl-manager.js info",
|
||||||
|
"ssl:delete": "node ssl-manager.js delete",
|
||||||
|
"data:create": "gulp data",
|
||||||
|
"config:create": "gulp configInit",
|
||||||
|
"config:reload": "gulp configReload",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"pugprettier:fix": "prettier --write '**/*.pug' --plugin='@prettier/plugin-pug'",
|
||||||
|
"stylelint": "stylelint 'src/scss/**/*.scss'",
|
||||||
|
"stylelint:fix": "stylelint 'src/scss/**/*.scss' --fix",
|
||||||
|
"eslint": "eslint .",
|
||||||
|
"gh-pages": "push-dir --dir=dist --branch=gh-pages",
|
||||||
|
"public": "pnpm run build && pnpm run gh-pages"
|
||||||
|
},
|
||||||
|
"author": "dreadwood",
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "9.16.0",
|
||||||
|
"autoprefixer": "10.4.19",
|
||||||
|
"browser-sync": "3.0.2",
|
||||||
|
"del": "7.1.0",
|
||||||
|
"dotenv": "^16.5.0",
|
||||||
|
"eslint": "9.16.0",
|
||||||
|
"globals": "15.13.0",
|
||||||
|
"gulp": "5.0.0",
|
||||||
|
"gulp-concat": "2.6.1",
|
||||||
|
"gulp-csso": "4.0.1",
|
||||||
|
"gulp-imagemin": "9.1.0",
|
||||||
|
"gulp-inject": "5.0.5",
|
||||||
|
"gulp-newer": "^1.4.0",
|
||||||
|
"gulp-order": "1.2.0",
|
||||||
|
"gulp-plumber": "1.2.1",
|
||||||
|
"gulp-postcss": "10.0.0",
|
||||||
|
"gulp-pug": "5.0.0",
|
||||||
|
"gulp-rename": "2.0.0",
|
||||||
|
"gulp-sass": "5.1.0",
|
||||||
|
"gulp-sourcemaps": "3.0.0",
|
||||||
|
"gulp-svgstore": "9.0.0",
|
||||||
|
"gulp-twig": "^1.2.0",
|
||||||
|
"gulp-uglify-es": "3.0.0",
|
||||||
|
"gulp-webp": "5.0.0",
|
||||||
|
"imagemin-avif": "0.1.6",
|
||||||
|
"nanoid": "5.0.8",
|
||||||
|
"postcss": "8.4.38",
|
||||||
|
"postcss-scss": "4.0.9",
|
||||||
|
"postcss-url": "10.1.3",
|
||||||
|
"pug": "3.0.2",
|
||||||
|
"push-dir": "0.4.1",
|
||||||
|
"sass": "1.72.0",
|
||||||
|
"stylelint": "16.3.1",
|
||||||
|
"stylelint-config-standard-scss": "13.0.0",
|
||||||
|
"stylelint-order": "6.0.4",
|
||||||
|
"stylelint-prettier": "5.0.2",
|
||||||
|
"uglify-js": "3.17.4"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"cwebp-bin",
|
||||||
|
"es5-ext",
|
||||||
|
"gifsicle",
|
||||||
|
"mozjpeg",
|
||||||
|
"optipng-bin",
|
||||||
|
"sharp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
7207
webpunk-templates/pnpm-lock.yaml
Normal file
15
webpunk-templates/readme.full.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Шаблон Webpunk для верстки
|
||||||
|
|
||||||
|
## Разработка
|
||||||
|
|
||||||
|
* Установить [pnpm](https://pnpm.io/)
|
||||||
|
* Установка - `pnpm i`
|
||||||
|
* Сборка проекта - `pnpm run build`
|
||||||
|
* Запуск локального сервера для разработки - `pnpm run dev`
|
||||||
|
* Запуск тестирования на соответствия код-гайдам - `pnpm run test`
|
||||||
|
* Обновить версию Github Pages (перед выполнением нужно собрать проект) - `gh-pages`
|
||||||
|
|
||||||
|
**Каталоги:**
|
||||||
|
|
||||||
|
* Все разработка ведёться в директории `src/`
|
||||||
|
* Итоговый код попадает в директорию `dist/`
|
||||||
160
webpunk-templates/readme.md
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
# Webpunk Templates — Подробная документация по сборщику
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
1. **Установите pnpm:**
|
||||||
|
[Инструкция по установке](https://pnpm.io/)
|
||||||
|
|
||||||
|
2. **Установите зависимости:**
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Запустите сборку или сервер разработки:**
|
||||||
|
- Сборка:
|
||||||
|
```bash
|
||||||
|
pnpm run build
|
||||||
|
```
|
||||||
|
- Сервер разработки:
|
||||||
|
```bash
|
||||||
|
pnpm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
- `src/` — исходные файлы (шаблоны, стили, скрипты, изображения, данные)
|
||||||
|
- `dist/` — результат сборки (готовый к публикации код)
|
||||||
|
- `ssl/` — dev SSL-сертификаты (создаются автоматически)
|
||||||
|
- `.env.local` — локальная конфигурация сборщика
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Основные возможности сборщика
|
||||||
|
|
||||||
|
### 1. **Гибкая работа с шаблонами**
|
||||||
|
|
||||||
|
- Поддержка **Pug** и **Twig** (выбор через `.env.local` или команды)
|
||||||
|
- Инкрементальная сборка страниц: при изменении страницы пересобирается только она
|
||||||
|
- Автоматическая подгрузка данных из `src/data/*.json` для шаблонов
|
||||||
|
- В шаблонах доступны функции для генерации srcset и mime-типов изображений
|
||||||
|
|
||||||
|
### 2. **Стили и скрипты**
|
||||||
|
|
||||||
|
- SCSS компиляция с sourcemaps, autoprefixer, postcss-url (автоматическое добавление версий к ассетам)
|
||||||
|
- Минификация CSS (`style.min.css`)
|
||||||
|
- Сборка и минификация JS (разделение на common/vendor)
|
||||||
|
- Поддержка sourcemaps для JS
|
||||||
|
|
||||||
|
### 3. **Изображения**
|
||||||
|
|
||||||
|
- Оптимизация PNG, JPEG, SVG
|
||||||
|
- Генерация webp и avif-версий изображений
|
||||||
|
- Инкрементальная обработка (обрабатываются только новые/изменённые файлы)
|
||||||
|
- Генерация SVG-спрайта и автоматическая инъекция в HTML
|
||||||
|
|
||||||
|
### 4. **Автоматизация и наблюдение**
|
||||||
|
|
||||||
|
- Watch-режим для всех исходников (шаблоны, данные, стили, скрипты, изображения, иконки)
|
||||||
|
- При изменении страницы пересобирается только она, при изменении общих файлов — пересобираются все страницы
|
||||||
|
- Автоматическая перезагрузка браузера через BrowserSync
|
||||||
|
|
||||||
|
### 5. **HTTPS для разработки**
|
||||||
|
|
||||||
|
- Автоматическое создание самоподписанных SSL-сертификатов для локальной разработки
|
||||||
|
- Включение HTTPS через `.env.local` или специальные команды
|
||||||
|
|
||||||
|
### 6. **Конфигурация**
|
||||||
|
|
||||||
|
- Все параметры (шаблонизатор, порт, хост, HTTPS, автооткрытие браузера и др.) настраиваются в `.env.local`
|
||||||
|
- Быстрое создание и обновление конфигурации через команды
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Основные команды
|
||||||
|
|
||||||
|
| Команда | Описание |
|
||||||
|
|------------------------|--------------------------------------------------------------------------|
|
||||||
|
| `pnpm run dev` | Запуск сервера разработки (шаблонизатор по умолчанию из `.env.local`) |
|
||||||
|
| `pnpm run dev:pug` | Запуск dev-сервера с Pug |
|
||||||
|
| `pnpm run dev:twig` | Запуск dev-сервера с Twig |
|
||||||
|
| `pnpm run dev:https` | Dev-сервер с HTTPS |
|
||||||
|
| `pnpm run dev:pug:https` | Dev-сервер с Pug и HTTPS |
|
||||||
|
| `pnpm run dev:twig:https` | Dev-сервер с Twig и HTTPS |
|
||||||
|
| `pnpm run build` | Сборка проекта (шаблонизатор по умолчанию) |
|
||||||
|
| `pnpm run build:pug` | Сборка с Pug |
|
||||||
|
| `pnpm run build:twig` | Сборка с Twig |
|
||||||
|
| `pnpm run ssl:create` | Создать dev SSL-сертификаты |
|
||||||
|
| `pnpm run ssl:check` | Проверить наличие SSL-сертификатов |
|
||||||
|
| `pnpm run ssl:info` | Информация о сертификатах |
|
||||||
|
| `pnpm run ssl:delete` | Удалить dev SSL-сертификаты |
|
||||||
|
| `pnpm run data:create` | Создать пример файла данных |
|
||||||
|
| `pnpm run config:create` | Создать `.env.local` с настройками |
|
||||||
|
| `pnpm run config:reload` | Перезагрузить конфиг без перезапуска сервера |
|
||||||
|
| `pnpm run clean` | Очистить папку `dist` |
|
||||||
|
| `pnpm run pugprettier:fix` | Применить prettier к pug-файлам |
|
||||||
|
| `pnpm run stylelint` | Проверить SCSS на ошибки |
|
||||||
|
| `pnpm run stylelint:fix` | Исправить ошибки SCSS автоматически |
|
||||||
|
| `pnpm run eslint` | Проверить JS на ошибки |
|
||||||
|
| `pnpm run gh-pages` | Залить содержимое `dist` на GitHub Pages |
|
||||||
|
| `pnpm run public` | Сборка и публикация на GitHub Pages |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Как работает сборщик
|
||||||
|
|
||||||
|
1. **Конфигурация**
|
||||||
|
При запуске читается `.env.local`. Если файла нет — создаётся автоматически.
|
||||||
|
Можно выбрать шаблонизатор, порт, хост, включить HTTPS и др.
|
||||||
|
|
||||||
|
2. **Сборка**
|
||||||
|
- Очищается папка `dist`
|
||||||
|
- Копируются ассеты (шрифты, изображения, фавиконки, robots.txt и др.)
|
||||||
|
- Компилируются стили и скрипты (с минификацией и sourcemaps)
|
||||||
|
- Оптимизируются изображения, генерируются webp/avif
|
||||||
|
- Генерируется SVG-спрайт
|
||||||
|
- Собираются HTML-файлы из шаблонов (Pug или Twig), подставляются данные из JSON
|
||||||
|
- В HTML автоматически инъектируется SVG-спрайт
|
||||||
|
|
||||||
|
3. **Dev-сервер**
|
||||||
|
- Запускается BrowserSync на выбранном порту/хосте
|
||||||
|
- При изменении страницы пересобирается только она (инкрементальная сборка)
|
||||||
|
- При изменении общих файлов (layout, mixins, data) пересобираются все страницы
|
||||||
|
- При изменении ассетов, стилей, скриптов — пересобираются только они
|
||||||
|
- Браузер автоматически перезагружается
|
||||||
|
|
||||||
|
4. **HTTPS**
|
||||||
|
- При первом запуске с HTTPS создаются dev-сертификаты (openssl обязателен)
|
||||||
|
- Сервер работает по https://
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Примеры использования
|
||||||
|
|
||||||
|
- **Сменить шаблонизатор:**
|
||||||
|
В `.env.local` изменить `TEMPLATE_ENGINE=pug` или `TEMPLATE_ENGINE=twig`, либо использовать команды `dev:pug`, `dev:twig`, `build:pug`, `build:twig`.
|
||||||
|
|
||||||
|
- **Включить HTTPS:**
|
||||||
|
В `.env.local` поставить `HTTPS=true` или использовать команду `dev:https`.
|
||||||
|
|
||||||
|
- **Добавить новые данные:**
|
||||||
|
Добавьте JSON-файл в `src/data/`, он автоматически попадёт в шаблоны.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Советы
|
||||||
|
|
||||||
|
- Для корректной работы HTTPS нужен установленный openssl.
|
||||||
|
- Для публикации на GitHub Pages используйте `pnpm run public`.
|
||||||
|
- Для быстрой генерации структуры данных используйте `pnpm run data:create`.
|
||||||
|
- Для обновления конфигурации без перезапуска сервера — `pnpm run config:reload`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Каталоги
|
||||||
|
|
||||||
|
- Исходники: `src/`
|
||||||
|
- Сборка: `dist/`
|
||||||
|
- Конфиг: `.env.local`
|
||||||
|
- Сертификаты: `ssl/`
|
||||||
66
webpunk-templates/src/data/content.json
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"version":"1.1.1",
|
||||||
|
"title":"Fonbet",
|
||||||
|
"landing": "ru",
|
||||||
|
"theme_color": "#030303",
|
||||||
|
"app_title":"Фонбет",
|
||||||
|
"description":"Fonbet",
|
||||||
|
"gtm_name":"openarena",
|
||||||
|
"landing_app": "openarena",
|
||||||
|
"event": "openarena",
|
||||||
|
"month": "ноября",
|
||||||
|
"prize_title": "Чтобы участвовать в розыгрыше нейромерча,\nнужно быть клиентом Фонбет и сделать ставку\nот 500 рублей на любое спортивное событие",
|
||||||
|
"close_land": "false",
|
||||||
|
"show_winners": "false",
|
||||||
|
"theme": "start",
|
||||||
|
"date_end": "20 ноября",
|
||||||
|
|
||||||
|
"form_theme": "dark",
|
||||||
|
"form_promo_hidden":"true",
|
||||||
|
"form_promo_id":"",
|
||||||
|
"form_promo_id_mob":"",
|
||||||
|
"gtag":"GTM-K7XNHT2",
|
||||||
|
"re_captcha":"6LehDGAUAAAAAJoqkx-oc6W-KeapSBCr2veF3Mwd",
|
||||||
|
"domain":"fon.bet",
|
||||||
|
"domain1":"fon.bet",
|
||||||
|
"cdn":"origin.bk6bba-resources.com/webStaticRed/promo",
|
||||||
|
"cdn_js":"origin.bk6bba-resources.com/webStaticRed/",
|
||||||
|
"rules_alias":"",
|
||||||
|
"rules_alias_mob":"",
|
||||||
|
"_freebet_link_modal":"https://fon.bet/promo/freebet_app_base/",
|
||||||
|
"form_link":"https://fonbet.onelink.me/Ci45/n3kg5eiy",
|
||||||
|
"app_link_mob":"https://fonbet.onelink.me/Ci45/n3kg5eiy",
|
||||||
|
"reg_link":"https://clicks.af-ru2e2e.com/click?offer_id=125&partner_id=4596&landing_id=4898&utm_medium=affiliate&sub_1=%7Bflashscore%7D&sub_2=%7Breg_ban%7D&sub_3=%7Bfb15k%7D",
|
||||||
|
"reg_link2":"https://fon.bet/account/registration/",
|
||||||
|
"join_link":"https://fon.bet/account/payments/deposit",
|
||||||
|
"auth_link":"https://fon.bet/account/",
|
||||||
|
"btn_link": "https://www.fon.bet/authProcess/login/",
|
||||||
|
"tg_link":"https://t.me/fonbetesports",
|
||||||
|
"_redirect_link": "",
|
||||||
|
"home":{
|
||||||
|
"title":"Fonbet"
|
||||||
|
},
|
||||||
|
"logo_header":{
|
||||||
|
"utm_source":"landing_openarena",
|
||||||
|
"utm_content":"header_logo",
|
||||||
|
"utm_term":"landing_openarena",
|
||||||
|
"partner":"landing_openarena"
|
||||||
|
},
|
||||||
|
"logo_footer":{
|
||||||
|
"src":"fon.bet.svg",
|
||||||
|
"src_dark":"fon.bet-white.svg",
|
||||||
|
"utm_source":"landing_openarena",
|
||||||
|
"utm_content":"footer_logo",
|
||||||
|
"utm_term":"landing_openarena",
|
||||||
|
"partner":"landing_openarena"
|
||||||
|
},
|
||||||
|
"match":{
|
||||||
|
"free_bet_value": "15000",
|
||||||
|
"free_bet": "15 000 ₽",
|
||||||
|
"free_bet_mob": "15 000 ₽",
|
||||||
|
"fonbet_link": "https://fon.bet/account/payments/deposit",
|
||||||
|
"fonbet_link_mob": "https://fon.bet/account/payments/deposit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
21
webpunk-templates/src/data/global.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"site": {
|
||||||
|
"title": "Мой сайт",
|
||||||
|
"description": "Описание сайта",
|
||||||
|
"author": "Автор"
|
||||||
|
},
|
||||||
|
"navigation": [
|
||||||
|
{
|
||||||
|
"title": "Главная",
|
||||||
|
"url": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "О нас",
|
||||||
|
"url": "/about"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Контакты",
|
||||||
|
"url": "/contacts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
webpunk-templates/src/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
5
webpunk-templates/src/favicon/favicon.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 16C0 7.16344 7.16344 0 16 0H104C112.837 0 120 7.16344 120 16V104C120 112.837 112.837 120 104 120H16C7.16344 120 0 112.837 0 104V16Z" fill="white"/>
|
||||||
|
<path d="M13.2681 0.079235C7.11546 1.04179 2.34834 5.19723 0.446184 11.3013C0.0234834 12.6629 0 14.3768 0 60.0161C0 105.656 0.0234834 107.369 0.446184 108.731C2.13699 114.131 5.87084 117.864 11.272 119.554C12.6341 119.977 14.3483 120 60 120C105.652 120 107.366 119.977 108.728 119.554C114.129 117.864 117.863 114.131 119.554 108.731C119.977 107.369 120 105.656 120 60.0161C120 14.3768 119.977 12.6629 119.554 11.3013C117.886 5.97197 114.387 2.40346 108.963 0.525299C107.554 0.032281 107.507 0.0322809 60.7045 0.00880389C34.9432 -0.0146732 13.5969 0.00880388 13.2681 0.079235ZM98.137 26.5848C98.3483 26.6553 98.5127 26.9839 98.5127 27.3126C98.5127 28.0404 95.8356 39.0981 95.3425 40.4598C95.1311 40.9997 94.7789 41.5162 94.5675 41.6336C94.3562 41.7275 87.7808 41.8684 79.9609 41.9388L65.7534 42.0562L64.2035 42.8075C62.5127 43.6526 61.7143 44.5682 61.0568 46.3994C60.7515 47.2681 59.2955 52.9965 59.2955 53.3487C59.2955 53.3956 65.4247 53.4426 72.9393 53.4426C81.8865 53.4426 86.7241 53.5365 87.0294 53.6773C87.2642 53.8182 87.4755 54.0999 87.4755 54.3347C87.4755 54.9451 84.9393 65.3924 84.5401 66.5193C83.8591 68.327 84.681 68.2331 69.3229 68.2331H55.6321L52.8611 79.549C49.409 93.7996 49.3386 94.0109 48.5871 94.2926C48.2583 94.4335 44.407 94.5274 39.6634 94.5274C31.6556 94.5274 31.3268 94.5039 30.8806 94.0579C30.6223 93.7996 30.411 93.4005 30.411 93.1657C30.411 92.4849 42.364 43.7935 42.8806 42.291C44.2427 38.5346 46.7789 34.614 49.3386 32.3367C52.6027 29.4255 57.0881 27.383 61.7847 26.7022C63.3581 26.4674 97.5734 26.3735 98.137 26.5848Z" fill="#e80024"/>
|
||||||
|
<path d="M13.2681 0.079235C7.11546 1.04179 2.34834 5.19723 0.446184 11.3013C0.0234834 12.6629 0 14.3768 0 60.0161C0 105.656 0.0234834 107.369 0.446184 108.731C2.13699 114.131 5.87084 117.864 11.272 119.554C12.6341 119.977 14.3483 120 60 120C105.652 120 107.366 119.977 108.728 119.554C114.129 117.864 117.863 114.131 119.554 108.731C119.977 107.369 120 105.656 120 60.0161C120 14.3768 119.977 12.6629 119.554 11.3013C117.886 5.97197 114.387 2.40346 108.963 0.525299C107.554 0.032281 107.507 0.0322809 60.7045 0.00880389C34.9432 -0.0146732 13.5969 0.00880388 13.2681 0.079235ZM98.137 26.5848C98.3483 26.6553 98.5127 26.9839 98.5127 27.3126C98.5127 28.0404 95.8356 39.0981 95.3425 40.4598C95.1311 40.9997 94.7789 41.5162 94.5675 41.6336C94.3562 41.7275 87.7808 41.8684 79.9609 41.9388L65.7534 42.0562L64.2035 42.8075C62.5127 43.6526 61.7143 44.5682 61.0568 46.3994C60.7515 47.2681 59.2955 52.9965 59.2955 53.3487C59.2955 53.3956 65.4247 53.4426 72.9393 53.4426C81.8865 53.4426 86.7241 53.5365 87.0294 53.6773C87.2642 53.8182 87.4755 54.0999 87.4755 54.3347C87.4755 54.9451 84.9393 65.3924 84.5401 66.5193C83.8591 68.327 84.681 68.2331 69.3229 68.2331H55.6321L52.8611 79.549C49.409 93.7996 49.3386 94.0109 48.5871 94.2926C48.2583 94.4335 44.407 94.5274 39.6634 94.5274C31.6556 94.5274 31.3268 94.5039 30.8806 94.0579C30.6223 93.7996 30.411 93.4005 30.411 93.1657C30.411 92.4849 42.364 43.7935 42.8806 42.291C44.2427 38.5346 46.7789 34.614 49.3386 32.3367C52.6027 29.4255 57.0881 27.383 61.7847 26.7022C63.3581 26.4674 97.5734 26.3735 98.137 26.5848Z" fill="black" fill-opacity="0.05"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
BIN
webpunk-templates/src/fonts/cera-pro-400.woff2
Normal file
BIN
webpunk-templates/src/fonts/cera-pro-500.woff2
Normal file
BIN
webpunk-templates/src/fonts/cera-pro-700.woff2
Normal file
BIN
webpunk-templates/src/fonts/cera-pro-900.woff2
Normal file
BIN
webpunk-templates/src/fonts/din-condensed-700.woff2
Normal file
3
webpunk-templates/src/icons/arrow-left-circle.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8m15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-4.5-.5a.5.5 0 0 1 0 1H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 288 B |
3
webpunk-templates/src/icons/arrow-right-circle.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8m15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0M4.5 7.5a.5.5 0 0 0 0 1h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 290 B |
BIN
webpunk-templates/src/img/martin-wettstein.jpg
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
webpunk-templates/src/img/owl copy.jpg
Normal file
|
After Width: | Height: | Size: 475 KiB |
BIN
webpunk-templates/src/img/owl.jpg
Normal file
|
After Width: | Height: | Size: 475 KiB |
BIN
webpunk-templates/src/img/pierre-lemos.jpg
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
22
webpunk-templates/src/js/common/_auth.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* auth.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
;(() => {
|
||||||
|
window.jsAuth = {
|
||||||
|
init() {
|
||||||
|
document.addEventListener('registrationCompleted', (evt) => {
|
||||||
|
const pin = evt.detail.clientId
|
||||||
|
if (!pin) return
|
||||||
|
|
||||||
|
console.log('userInfoUpdated')
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('userInfoUpdated', (evt) => {
|
||||||
|
if (evt.detail.clientId !== evt.detail.prevClientId) {
|
||||||
|
console.log('userInfoUpdated')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
34
webpunk-templates/src/js/common/_fetch.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
* fetch.js
|
||||||
|
*/
|
||||||
|
;(() => {
|
||||||
|
window.jsFetch = {
|
||||||
|
/**
|
||||||
|
* wrapper for fetch
|
||||||
|
* @param {string} url
|
||||||
|
* @param {string} method
|
||||||
|
* @param {JSON} data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async sendData(url, data, method = 'GET') {
|
||||||
|
const options = {
|
||||||
|
method,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: data ? JSON.stringify(data) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, options)
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error('Response error:', res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return await res.json()
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Request catch error:', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
30
webpunk-templates/src/js/common/_lib.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* lib.js
|
||||||
|
*/
|
||||||
|
;(() => {
|
||||||
|
window.jsLib = {
|
||||||
|
/** @type {MediaQueryList} */
|
||||||
|
mqTablet: window.matchMedia(window.jsUtils.BREAKPOINT_TABLET),
|
||||||
|
|
||||||
|
/** @type {MediaQueryList} */
|
||||||
|
mqDesktop: window.matchMedia(window.jsUtils.BREAKPOINT_DESKTOP),
|
||||||
|
|
||||||
|
getGapSwiper(gap, tabletGap, desktopGap) {
|
||||||
|
let rate
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
case this.mqDesktop.matches:
|
||||||
|
rate = desktopGap / window.jsUtils.DESKTOP_WIDTH
|
||||||
|
break
|
||||||
|
case this.mqTablet.matches:
|
||||||
|
rate = tabletGap / window.jsUtils.TABLET_WIDTH
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
rate = gap / window.jsUtils.MOBILE_WIDTH
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return document.documentElement.scrollWidth * rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
96
webpunk-templates/src/js/common/_pin.js
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
* pin.js
|
||||||
|
*/
|
||||||
|
;(() => {
|
||||||
|
window.jsPin = {
|
||||||
|
init() {
|
||||||
|
let regFlag = false
|
||||||
|
|
||||||
|
let refreshIntervalId = setInterval(() => {
|
||||||
|
if (typeof window.registrationApi !== 'undefined') regFlag = true
|
||||||
|
if (regFlag) {
|
||||||
|
clearInterval(refreshIntervalId)
|
||||||
|
document.dispatchEvent(new Event('registrationInit'))
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
registrationApi.onRegistrationStateChanged = (state) => {
|
||||||
|
console.log('registrationApi state', state)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
registrationApi.onRegistrationCompleted = (clientId) => {
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent('registrationCompleted', {
|
||||||
|
detail: { clientId: clientId }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
console.log('Registration completed', clientId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
const userInfo = (() => {
|
||||||
|
let clientId = null
|
||||||
|
let prevClientId = null
|
||||||
|
let fsid = null
|
||||||
|
|
||||||
|
const getCookie = (name) => {
|
||||||
|
const matches = document.cookie.match(
|
||||||
|
new RegExp(
|
||||||
|
`(?:^|; )${name.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1')}=([^;]*)`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return matches ? decodeURIComponent(matches[1]) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const getParameterByName = (name, url = window.location.href) => {
|
||||||
|
name = name.replace(/[[\]]/g, '\\$&')
|
||||||
|
const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`)
|
||||||
|
const results = regex.exec(url)
|
||||||
|
if (!results) return null
|
||||||
|
if (!results[2]) return ''
|
||||||
|
return decodeURIComponent(results[2].replace(/\+/g, ' '))
|
||||||
|
}
|
||||||
|
|
||||||
|
const setClientID = () => {
|
||||||
|
prevClientId = clientId
|
||||||
|
clientId =
|
||||||
|
getParameterByName('clientId') ||
|
||||||
|
getCookie('headerApi.cid') ||
|
||||||
|
getParameterByName('fsid')
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFSID = () => {
|
||||||
|
fsid = getCookie('headerApi.fsid') || getCookie('headerApi.FSID')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVisibilityChange = () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
setClientID()
|
||||||
|
const event = new CustomEvent('userInfoUpdated', {
|
||||||
|
detail: {
|
||||||
|
clientId,
|
||||||
|
prevClientId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
document.dispatchEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setClientID()
|
||||||
|
setFSID()
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||||
|
|
||||||
|
return {
|
||||||
|
getClientID: () => clientId,
|
||||||
|
getPrevClientID: () => prevClientId,
|
||||||
|
setClientID,
|
||||||
|
getFSID: () => fsid,
|
||||||
|
setFSID,
|
||||||
|
handleVisibilityChange
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
window.userInfo = userInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
90
webpunk-templates/src/js/common/_utils.js
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* utils.js
|
||||||
|
*/
|
||||||
|
;(() => {
|
||||||
|
window.jsUtils = {
|
||||||
|
BREAKPOINT_DESKTOP: '(min-width: 1024px)',
|
||||||
|
MOBILE_WIDTH: 360,
|
||||||
|
DESKTOP_WIDTH: 1920,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show html element
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
*/
|
||||||
|
showEl(el) {
|
||||||
|
if (!(el instanceof Node)) return
|
||||||
|
el.removeAttribute('hidden')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hide html element
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
*/
|
||||||
|
hideEl(el) {
|
||||||
|
if (!(el instanceof Node)) return
|
||||||
|
el.setAttribute('hidden', 'hidden')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example: 'балл', 'балла', 'баллов'
|
||||||
|
* @param {number} number
|
||||||
|
* @param {string} one
|
||||||
|
* @param {string} few
|
||||||
|
* @param {string} many
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
pluralize(number, one, few, many) {
|
||||||
|
const lastDigit = number % 10
|
||||||
|
const lastTwoDigits = number % 100
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
case lastDigit === 1:
|
||||||
|
case lastTwoDigits !== 11:
|
||||||
|
return one
|
||||||
|
case lastDigit >= 2:
|
||||||
|
case lastDigit <= 4:
|
||||||
|
case !(lastTwoDigits >= 12 && lastTwoDigits <= 14):
|
||||||
|
return few
|
||||||
|
default:
|
||||||
|
return many
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get random integer
|
||||||
|
* @param {number} min
|
||||||
|
* @param {number} max
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
randomInteger(min, max) {
|
||||||
|
const rand = min + Math.random() * (max + 1 - min)
|
||||||
|
return Math.floor(rand)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {(...args: any[]) => T} func
|
||||||
|
* @param {number} delay
|
||||||
|
* @returns {(...args: any[]) => void}
|
||||||
|
*/
|
||||||
|
throttle(func, delay) {
|
||||||
|
let lastCall = 0
|
||||||
|
|
||||||
|
return function (...args) {
|
||||||
|
const now = new Date().getTime()
|
||||||
|
|
||||||
|
if (now - lastCall >= delay) {
|
||||||
|
lastCall = now
|
||||||
|
func.apply(this, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
detectSafari() {
|
||||||
|
return /^((?!chrome|android|crios|fxios).)*safari/i.test(
|
||||||
|
navigator.userAgent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
8
webpunk-templates/src/js/common/index.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* index.js
|
||||||
|
*/
|
||||||
|
;(() => {
|
||||||
|
// window.jsPin.init()
|
||||||
|
// window.jsAuth.init()
|
||||||
|
|
||||||
|
})()
|
||||||
0
webpunk-templates/src/js/vendor/.gitkeep
vendored
Normal file
2
webpunk-templates/src/pug/components/block/b-footer.pug
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
mixin b-footer({ className })
|
||||||
|
footer.b-footer(class=className)
|
||||||
2
webpunk-templates/src/pug/components/block/b-header.pug
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
mixin b-header({ className })
|
||||||
|
header.b-header(class=className)
|
||||||
0
webpunk-templates/src/pug/components/modal/.gitkeep
Normal file
0
webpunk-templates/src/pug/data/.gitkeep
Normal file
5
webpunk-templates/src/pug/data/global.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"site": {
|
||||||
|
"title": "Мой сайт"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
webpunk-templates/src/pug/elements/console.pug
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
mixin console({ data, id })
|
||||||
|
script
|
||||||
|
if data && id
|
||||||
|
| const pugConsole#{id} = !{JSON.stringify(data)};
|
||||||
|
| console.log(pugConsole#{id});
|
||||||
|
else
|
||||||
|
| console.log('Pug console params are not complete');
|
||||||
5
webpunk-templates/src/pug/elements/favicon.pug
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
mixin favicon({ })
|
||||||
|
link(rel="icon" href=`${baseUrl}/favicon.ico`)
|
||||||
|
link(rel="icon" href=`${baseUrl}/favicon/favicon.svg?v=${searchId}` type="image/svg+xml")
|
||||||
|
link(rel="icon" href="https://origin.bk6bba-resources.com/webStaticRed/promo/lands/images/favicons-en/android-chrome-192x192.png" type="image/png")
|
||||||
|
link(rel="apple-touch-icon" href="https://origin.bk6bba-resources.com/webStaticRed/promo/lands/images/favicons-en/apple-touch-icon.png")
|
||||||
23
webpunk-templates/src/pug/elements/head.pug
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
include /elements/favicon
|
||||||
|
//- include /script/s-gtm
|
||||||
|
//- include /script/s-yametrika
|
||||||
|
include /elements/open-graph
|
||||||
|
|
||||||
|
mixin head({ title, gtmId, yaMetrikaId })
|
||||||
|
head
|
||||||
|
meta(charset="utf-8")
|
||||||
|
meta(name="viewport" content="width=device-width, initial-scale=1")
|
||||||
|
//- meta(name="gtm_name" content="alias path")
|
||||||
|
|
||||||
|
title=title
|
||||||
|
|
||||||
|
+favicon({})
|
||||||
|
//- +open-graph({})
|
||||||
|
|
||||||
|
link(rel="stylesheet" href=`${baseUrl}/css/style.css?v=${searchId}`)
|
||||||
|
|
||||||
|
//- +s-gtm({ id: gtmId })
|
||||||
|
//- +s-yametrika({ id: yaMetrikaId })
|
||||||
|
|
||||||
|
//- UTM
|
||||||
|
//- script(src="https://origin.bk6bba-resources.com/webStaticRed/promo/lands/scripts/utm.js")
|
||||||
54
webpunk-templates/src/pug/elements/images.pug
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
//- content raster image
|
||||||
|
mixin img({ src, w, h, alt, isLazy = true, className, isVersion = true })
|
||||||
|
- const sParams = isVersion ? `?v=${searchId}` : ""
|
||||||
|
- const url = src ? `${baseUrl}${src}${sParams}` : undefined
|
||||||
|
img(
|
||||||
|
class=className
|
||||||
|
src=url
|
||||||
|
width=w
|
||||||
|
height=h
|
||||||
|
loading=isLazy && "lazy"
|
||||||
|
alt=(alt || "")
|
||||||
|
)
|
||||||
|
|
||||||
|
//- simple raster image
|
||||||
|
mixin picture({ src, w, h, alt, isLazy = true, className, isVersion = true })
|
||||||
|
- const path = src.split(".")[0]
|
||||||
|
- const sParams = isVersion ? `?v=${searchId}` : ""
|
||||||
|
picture
|
||||||
|
source(type="image/avif" srcset=`${baseUrl}${path}.avif${sParams}`)
|
||||||
|
source(type="image/webp" srcset=`${baseUrl}${path}.webp${sParams}`)
|
||||||
|
img(
|
||||||
|
class=className
|
||||||
|
src=`${baseUrl}${src}?v=${sParams}`
|
||||||
|
width=w
|
||||||
|
height=h
|
||||||
|
loading=isLazy && "lazy"
|
||||||
|
alt=(alt || "")
|
||||||
|
)
|
||||||
|
|
||||||
|
//- retina raster image
|
||||||
|
mixin picture2x({ src, w, h, alt, isLazy = true, className, isVersion = true })
|
||||||
|
- const path = src.split("@")[0]
|
||||||
|
- const format = src.split(".")[1]
|
||||||
|
- const sParams = isVersion ? `?v=${searchId}` : ""
|
||||||
|
picture
|
||||||
|
source(type=getMime("avif") srcset=getSrcset(path, "avif", sParams))
|
||||||
|
source(type=getMime("webp") srcset=getSrcset(path, "webp", sParams))
|
||||||
|
img(
|
||||||
|
class=className
|
||||||
|
src=`${baseUrl}${src}${sParams}`
|
||||||
|
srcset=`${baseUrl}${path}@2x.${format}${sParams} 2x`
|
||||||
|
width=w
|
||||||
|
height=h
|
||||||
|
loading=isLazy && "lazy"
|
||||||
|
alt=(alt || "")
|
||||||
|
)
|
||||||
|
|
||||||
|
//- simple vector image
|
||||||
|
mixin svg({ name, w, h, className })
|
||||||
|
if name.includes('/img')
|
||||||
|
img(class=className || null src=`${baseUrl}${name}?v=${searchId}` width=w height=h alt="")
|
||||||
|
else
|
||||||
|
svg(class=className || null width=w height=h)
|
||||||
|
use(xlink:href=`#${name}`)
|
||||||
30
webpunk-templates/src/pug/elements/layout-page.pug
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
include /elements/head
|
||||||
|
include /elements/images
|
||||||
|
include /elements/sprite
|
||||||
|
//- include /script/s-gtm-noscript
|
||||||
|
//- include /script/s-yametrika-noscript
|
||||||
|
//- include /script/s-pin
|
||||||
|
include /components/block/b-header
|
||||||
|
include /components/block/b-footer
|
||||||
|
|
||||||
|
- let title = ""
|
||||||
|
- let gtmId = ""
|
||||||
|
- let yaMetrikaId = 0
|
||||||
|
|
||||||
|
block data
|
||||||
|
|
||||||
|
doctype html
|
||||||
|
html(lang="ru")
|
||||||
|
+head({ title, gtmId, yaMetrikaId })
|
||||||
|
body
|
||||||
|
//- +s-gtm-noscript({ id: gtmId })
|
||||||
|
//- +s-yametrika-noscript({ id: yaMetrikaId })
|
||||||
|
//- +s-pin({})
|
||||||
|
block libjs
|
||||||
|
+sprite({})
|
||||||
|
+b-header({})
|
||||||
|
main
|
||||||
|
block content
|
||||||
|
+b-footer({})
|
||||||
|
block modal
|
||||||
|
include /elements/script
|
||||||
7
webpunk-templates/src/pug/elements/open-graph.pug
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
mixin open-graph({})
|
||||||
|
meta(property='og:title' content='{описание сайта}')
|
||||||
|
meta(property='og:description' content='{описание страницы}')
|
||||||
|
meta(property='og:image' content='{изображение для превью, не меньше 200х200}')
|
||||||
|
meta(property='og:type' content='website')
|
||||||
|
meta(property='og:url' content='{адрес сайта}')
|
||||||
|
meta(property='og:locale' content='ru_RU')
|
||||||
2
webpunk-templates/src/pug/elements/script.pug
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
//- script(src=`${baseUrl}/js/vendor.js?v=${searchId}`)
|
||||||
|
script(src=`${baseUrl}/js/script.js?v=${searchId}`)
|
||||||
4
webpunk-templates/src/pug/elements/sprite.pug
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
mixin sprite({ className })
|
||||||
|
.visually-hidden(class=className)
|
||||||
|
// inject:svg
|
||||||
|
// endinject
|
||||||
15
webpunk-templates/src/pug/pages/about.pug
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
extends /elements/layout-page
|
||||||
|
|
||||||
|
block data
|
||||||
|
- title = "About Template"
|
||||||
|
|
||||||
|
block content
|
||||||
|
.container
|
||||||
|
.row
|
||||||
|
.col-md-12
|
||||||
|
h1 About Template
|
||||||
|
p This is a template page.
|
||||||
|
p You can use this as a starting point for your own pages.
|
||||||
|
p Feel free to modify the content and structure as needed
|
||||||
|
|
||||||
|
block modal
|
||||||
21
webpunk-templates/src/pug/pages/index.pug
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
extends /elements/layout-page
|
||||||
|
//- include /script/s-match
|
||||||
|
|
||||||
|
block data
|
||||||
|
- title = "Template"
|
||||||
|
|
||||||
|
|
||||||
|
block libjs
|
||||||
|
//- +s-match({})
|
||||||
|
//- script(src="https://origin.bk6bba-resources.com/webStaticRed/promo/lands/scripts/marked.min.js")
|
||||||
|
|
||||||
|
block content
|
||||||
|
.container
|
||||||
|
.row
|
||||||
|
.col-md-12
|
||||||
|
h1 Template
|
||||||
|
p This is a template page.
|
||||||
|
p You can use this as a starting point for your own pages.
|
||||||
|
p Feel free to modify the content and structure as needed
|
||||||
|
|
||||||
|
block modal
|
||||||
4
webpunk-templates/src/pug/script/s-gtm-noscript.pug
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
mixin s-gtm-noscript({ id })
|
||||||
|
// Google Tag Manager (noscript)
|
||||||
|
noscript
|
||||||
|
iframe(src=`https://www.googletagmanager.com/ns.html?id=${id}` height="0" width="0" style="display:none;visibility:hidden")
|
||||||
11
webpunk-templates/src/pug/script/s-gtm.pug
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
mixin s-gtm({ id })
|
||||||
|
// Google Tag Manager
|
||||||
|
script.
|
||||||
|
(function(w,d,s,l,i){
|
||||||
|
w[l]=w[l]||[];
|
||||||
|
w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});
|
||||||
|
var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';
|
||||||
|
j.async=true;
|
||||||
|
j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
|
||||||
|
f.parentNode.insertBefore(j,f);
|
||||||
|
})(window,document,'script','dataLayer','#{id}');
|
||||||
51
webpunk-templates/src/pug/script/s-match.pug
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
mixin s-match({})
|
||||||
|
//- match and odds
|
||||||
|
script.
|
||||||
|
function initMatch() {
|
||||||
|
if (!data) return
|
||||||
|
|
||||||
|
for (var key in data) {
|
||||||
|
render(key, data[key])
|
||||||
|
}
|
||||||
|
for (var key in conf) {
|
||||||
|
render(key, conf[key])
|
||||||
|
}
|
||||||
|
utm();
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(key, value) {
|
||||||
|
document.querySelectorAll('[data-content="' + key + '"]').forEach((el) => {
|
||||||
|
if (value) {
|
||||||
|
switch (el.tagName) {
|
||||||
|
case 'LABEL':
|
||||||
|
case 'DIV':
|
||||||
|
el.style.display = 'block'
|
||||||
|
break
|
||||||
|
case 'SPAN':
|
||||||
|
case 'P':
|
||||||
|
el.innerHTML = value
|
||||||
|
break
|
||||||
|
case 'TEXTAREA':
|
||||||
|
case 'INPUT':
|
||||||
|
el.value = value
|
||||||
|
break
|
||||||
|
case 'A':
|
||||||
|
el.href = value;
|
||||||
|
break
|
||||||
|
case 'IFRAME':
|
||||||
|
case 'VIDEO':
|
||||||
|
case 'IMG':
|
||||||
|
el.src = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
el.style.display = 'none'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
script.
|
||||||
|
window.lang = 'ru'
|
||||||
|
window.alias = 'dota2'
|
||||||
|
window.callback = 'initMatch'
|
||||||
|
script(src="https://text.ajaxfeed.com/texts/line.js")
|
||||||
|
|
||||||
10
webpunk-templates/src/pug/script/s-pin.pug
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
mixin s-pin({})
|
||||||
|
script.
|
||||||
|
window.initPin = () => {
|
||||||
|
[urlsConfig.registrationWidgetLoader].forEach((_item) => {
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.src = urlsConfig.cdnUrl + '/' + _item
|
||||||
|
document.head.appendChild(script);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
script(src="https://fon.bet/settings.js" onload="initPin()")
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
mixin s-yametrika-noscript({ id })
|
||||||
|
// Yandex Metrika (noscript)
|
||||||
|
noscript
|
||||||
|
div #[img(src=`https://mc.yandex.ru/watch/${id}` style="position:absolute; left:-9999px;" alt="")]
|
||||||
19
webpunk-templates/src/pug/script/s-yametrika.pug
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
mixin s-yametrika({ id })
|
||||||
|
// Yandex.Metrika counter
|
||||||
|
script(type="text/javascript").
|
||||||
|
(function(m,e,t,r,i,k,a){
|
||||||
|
m[i]=m[i] || function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||||
|
m[i].l=1*new Date();
|
||||||
|
for (var j = 0; j < document.scripts.length; j++) {
|
||||||
|
if (document.scripts[j].src === r) { return; }
|
||||||
|
}
|
||||||
|
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
|
||||||
|
})(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
|
||||||
|
|
||||||
|
ym(#{id}, "init", {
|
||||||
|
clickmap: true,
|
||||||
|
trackLinks: true,
|
||||||
|
accurateTrackBounce: true,
|
||||||
|
webvisor: true,
|
||||||
|
trackHash: true
|
||||||
|
});
|
||||||
2
webpunk-templates/src/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
.b-footer {
|
||||||
|
width: 100%;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-right: $padding;
|
||||||
|
padding-left: $padding;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
.b-header {
|
||||||
|
width: 100%;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-right: $padding;
|
||||||
|
padding-left: $padding;
|
||||||
|
}
|
||||||
2
webpunk-templates/src/scss/components/block/index.scss
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
@import "b-footer";
|
||||||
|
@import "b-header";
|
||||||
1
webpunk-templates/src/scss/components/index.scss
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
@import "block";
|
||||||
59
webpunk-templates/src/scss/global/_basic.scss
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
font-size: max(1px, calc(100vw / 360));
|
||||||
|
@include desktop {
|
||||||
|
font-size: calc(100vw / 1920);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: $container-min;
|
||||||
|
min-height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: $font-default;
|
||||||
|
font-size: 16rem;
|
||||||
|
line-height: 22rem;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
text-align: left;
|
||||||
|
color: $color-black;
|
||||||
|
word-break: break-word;
|
||||||
|
background-color: $color-white;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
picture {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
height: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
&:not([disabled]) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[hidden] {
|
||||||
|
/* stylelint-disable-next-line declaration-no-important */
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
8
webpunk-templates/src/scss/global/_colors.scss
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
$color-gray-965: #f6f6f6; // hsl(0, 0%, 96.5%)
|
||||||
|
$color-gray-212: #363636; // hsl(0, 0%, 21.2%)
|
||||||
|
|
||||||
|
$color-white: #ffffff;
|
||||||
|
$color-black: #000000;
|
||||||
|
$color-blue: #2750d3;
|
||||||
|
$color-yellow: #ffd00e;
|
||||||
|
$color-red: #fe0000;
|
||||||
39
webpunk-templates/src/scss/global/_fonts.scss
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: "Cera Pro";
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("../fonts/cera-pro-400.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Cera Pro";
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("../fonts/cera-pro-500.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Cera Pro";
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("../fonts/cera-pro-700.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Cera Pro";
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("../fonts/cera-pro-900.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "DIN Condensed";
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("../fonts/din-condensed-700.woff2") format("woff2");
|
||||||
|
}
|
||||||
17
webpunk-templates/src/scss/global/_keyframes.scss
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
@keyframes show-opacity {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes hide-opacity {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
webpunk-templates/src/scss/global/_mixins.scss
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
@mixin desktop {
|
||||||
|
@media (min-width: $desktop) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin ultra-wide {
|
||||||
|
@media (aspect-ratio >= 2 / 1) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin list-reset {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin hide-scroll {
|
||||||
|
scrollbar-width: none;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin transition($args...) {
|
||||||
|
transition-timing-function: ease-out;
|
||||||
|
transition-duration: 0.2s;
|
||||||
|
transition-property: $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin word-break {
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin text-truncate {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin text-truncate-multiline($row-count: 3) {
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: $row-count;
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
18
webpunk-templates/src/scss/global/_variables.scss
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// fonts
|
||||||
|
$font-default: "Cera Pro", sans-serif;
|
||||||
|
$font-title: "DIN Condensed", sans-serif;
|
||||||
|
|
||||||
|
// retina
|
||||||
|
$retina-dpi: 144dpi;
|
||||||
|
$retina-dppx: 1.5dppx;
|
||||||
|
|
||||||
|
// container
|
||||||
|
$container-max: 1920px;
|
||||||
|
$container-min: 375px;
|
||||||
|
|
||||||
|
// breakpoints
|
||||||
|
$desktop: 1024px;
|
||||||
|
|
||||||
|
// padding
|
||||||
|
$padding: 10px;
|
||||||
|
$padding-desktop: 20px;
|
||||||
5
webpunk-templates/src/scss/global/index.scss
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
@import "variables";
|
||||||
|
@import "fonts";
|
||||||
|
@import "colors";
|
||||||
|
@import "mixins";
|
||||||
|
@import "basic";
|
||||||
14
webpunk-templates/src/scss/helpers/_overlay.scss
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
.overlay {
|
||||||
|
@include hide-scroll;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 10px;
|
||||||
|
overflow: scroll;
|
||||||
|
background-color: transparentize($color-black, 0.8);
|
||||||
|
&.show {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
webpunk-templates/src/scss/helpers/_scroll-lock.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.scroll-lock {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
15
webpunk-templates/src/scss/helpers/_visually-hidden.scss
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
.visually-hidden:not(:focus, :active),
|
||||||
|
input[type="checkbox"].visually-hidden,
|
||||||
|
input[type="radio"].visually-hidden,
|
||||||
|
input[type="file"].visually-hidden {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
clip-path: inset(100%);
|
||||||
|
}
|
||||||
3
webpunk-templates/src/scss/helpers/index.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
@import "overlay";
|
||||||
|
@import "scroll-lock";
|
||||||
|
@import "visually-hidden";
|
||||||
11
webpunk-templates/src/scss/index.scss
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
// In the symphony of life,
|
||||||
|
// order orchestrates the melody.
|
||||||
|
|
||||||
|
@use "sass:color";
|
||||||
|
@use "sass:math";
|
||||||
|
|
||||||
|
//
|
||||||
|
@import "vendor";
|
||||||
|
@import "global";
|
||||||
|
@import "helpers";
|
||||||
|
@import "components";
|
||||||
1
webpunk-templates/src/scss/vendor/index.scss
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
// @import "";
|
||||||
11
webpunk-templates/src/twig/layouts/_footer.twig
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<footer class="l-footer b-footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="column small-12 text-center">
|
||||||
|
<p class="copy">
|
||||||
|
Lorem ipsum dolor sit amet, <br>consectetur adipisicing.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
0
webpunk-templates/src/twig/layouts/_head.twig
Normal file
24
webpunk-templates/src/twig/layouts/_header.twig
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<header class="l-header b-header m-abs">
|
||||||
|
<div class="container b-header__wrp">
|
||||||
|
<div class="row align-middle">
|
||||||
|
<div class="column b-header__logo shrink">
|
||||||
|
<a href="./" class="b-logo">
|
||||||
|
<img class="b-logo--header" src="./img/logo.svg"/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="column b-header__menu shrink hide-for-small-only">
|
||||||
|
<nav class="b-header__menu-nav">
|
||||||
|
<ul class="inline-list">
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="column shrink b-header__burger">
|
||||||
|
<a class="menu-icon burger hide-for-large" data-menu-trigger="">
|
||||||
|
<span class="b-burger"><i></i></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
117
webpunk-templates/src/twig/layouts/_main-lp.twig
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0">
|
||||||
|
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
<meta name="description" content="{{ description }}">
|
||||||
|
<meta name="gtm_name" content="{{ gtm_name }}">
|
||||||
|
<meta name="cdn" content="./">
|
||||||
|
<meta name="img_src" content="./img/bg/">
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="https://{{ cdn }}/lands/images/favicons-en/favicon.ico">
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="https://{{ cdn }}/lands/images/favicons-en/favicon-16x16.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="https://{{ cdn }}/lands/images/favicons-en/favicon-32x32.png">
|
||||||
|
<meta name="theme-color" content="{{ theme_color }}">
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="https://{{ cdn }}/lands/images/favicons-en/android-chrome-192x192.png">
|
||||||
|
<link rel="manifest" href="https://{{ cdn }}/lands/images/favicons-en/manifest.json">
|
||||||
|
|
||||||
|
<meta name="apple-mobile-web-app-title" content="{{ app_title }}">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
|
<link rel="apple-touch-icon" href="https://{{ cdn }}/lands/images/favicons-en/apple-touch-icon.png">
|
||||||
|
|
||||||
|
<meta name="application-name" content="{{ app_title }}">
|
||||||
|
<meta name="msapplication-navbutton-color" content="#db2532">
|
||||||
|
<meta name="msapplication-TileColor" content="#2b5797">
|
||||||
|
<meta name="msapplication-TileImage" content="https://{{ cdn }}/lands/images/favicons-en/mstile-150x150.png">
|
||||||
|
<meta name="msapplication-config" content="https://{{ cdn }}/lands/images/favicons-en/browserconfig.xml">
|
||||||
|
|
||||||
|
<link rel="mask-icon" type="image/png" href="https://{{ cdn }}/lands/images/favicons-en/safari-pinned-tab.svg" color="#db2532">
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://{{ cdn }}/lands/styles/fonts.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://{{ cdn }}/lucid/styles/lucid.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://{{ cdn }}/lands/styles/markdown.css">
|
||||||
|
{# <link rel="stylesheet" type="text/css" href="https://{{ cdn }}/lucid/styles/ie10flex.css">#}
|
||||||
|
|
||||||
|
|
||||||
|
{% if {{ re_captcha }} %}
|
||||||
|
<!--<script src="https://www.google.com/recaptcha/api.js?render={{ re_captcha }}"></script>-->
|
||||||
|
{% endif %}
|
||||||
|
<script src="https://{{ cdn }}/lands/scripts/marked.min.js"></script>
|
||||||
|
<script src="https://{{ cdn }}/lands/scripts/jquery.min.js"></script>
|
||||||
|
{#<script src="https://{{ cdn }}/lands/scripts/jquery.cookie.js"></script>#}
|
||||||
|
{# <script src="https://{{ cdn }}/lands/scripts/jquery.form.min.js"></script>#}
|
||||||
|
<script src="https://{{ cdn }}/lands/scripts/jquery.mask.min.js"></script>
|
||||||
|
{# <script src="https://{{ cdn }}/lands/scripts/owl.carousel.min.js"></script>#}
|
||||||
|
<script src="https://{{ cdn }}/lands/scripts/utm.js"></script>
|
||||||
|
{% if show_qr == 'true' %}
|
||||||
|
<script src="https://{{ cdn }}/lands/scripts/qrcode.min.js"></script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# <script src="https://text.ajaxfeed.com/texts/line.js"></script>#}
|
||||||
|
|
||||||
|
<!-- Google Tag Manager -->
|
||||||
|
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||||
|
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||||
|
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||||
|
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||||
|
})(window,document,'script','dataLayer','GTM-M35HZTK6');</script>
|
||||||
|
<!-- End Google Tag Manager -->
|
||||||
|
|
||||||
|
<!-- Yandex.Metrika counter -->
|
||||||
|
<script type="text/javascript" > (function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)}; m[i].l=1*new Date(); for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }} k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)}) (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym"); ym(40810904, "init", { clickmap:true, trackLinks:true, accurateTrackBounce:true, webvisor:true, trackHash:true }); </script>
|
||||||
|
<!-- /Yandex.Metrika counter -->
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<link rel="stylesheet" href="./css/foundation-lib.css?v={{ version }}">
|
||||||
|
<link rel="stylesheet" href="./css/style.css?v={{ version }}">
|
||||||
|
<script type="text/javascript" src="./js/app.js?v={{ version }}"></script>
|
||||||
|
<script type="text/javascript" src="./js/auth.js?v={{ version }}"></script>
|
||||||
|
<script type="text/javascript" src="./js/script.js?v={{ version }}"></script>
|
||||||
|
|
||||||
|
{% block extraJS %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block preload %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</head>
|
||||||
|
{% block bodyBegin %}
|
||||||
|
<body class="{{ body_class }} {% if theme%}theme-{{ theme }} {% endif %}{% if landing%}landing-{{ landing }}{% endif %}{% if class %} {{ class }}{% endif %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<!-- Google Tag Manager (noscript) -->
|
||||||
|
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-M35HZTK6"
|
||||||
|
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||||
|
<!-- End Google Tag Manager (noscript) -->
|
||||||
|
|
||||||
|
{% block modal %}
|
||||||
|
{% include 'layouts/_modal.twig' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<div class="l-wrapper">
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
{% include "layouts/_header.twig" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
<main class="l-main" id="main">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
{% include "layouts/_footer.twig" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% block extraJsLib %}{% endblock %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
75
webpunk-templates/src/twig/layouts/_main.twig
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0">
|
||||||
|
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:site_name" content="">
|
||||||
|
<meta property="og:title" content="{{ title }}">
|
||||||
|
<meta property="og:description" content="{{ description }}">
|
||||||
|
<meta property="og:url" content="">
|
||||||
|
<meta property="og:locale" content="ru_RU">
|
||||||
|
<meta property="og:image" content="img/logo.png"/>
|
||||||
|
<meta property="og:image:type" content="image/png"/>
|
||||||
|
<meta property="og:image:width" content="300">
|
||||||
|
<meta property="og:image:height" content="200">
|
||||||
|
|
||||||
|
<meta name="twitter:card" content="summary_large_image"/>
|
||||||
|
<meta name="twitter:url" content=""/>
|
||||||
|
<meta name="twitter:title" content="{{ title }}"/>
|
||||||
|
<meta name="twitter:description" content="{{ description }}"/>
|
||||||
|
<meta name="twitter:creator" content="@rudenich_"/>
|
||||||
|
<meta name="twitter:image:src" content="img/logo.png"/>
|
||||||
|
<meta name="twitter:image:width" content="300"/>
|
||||||
|
<meta name="twitter:image:height" content="200"/>
|
||||||
|
<meta name="twitter:site" content=""/>
|
||||||
|
<script>!function(c){"use strict";function e(e,t,n,o){var r,i=c.document,d=i.createElement("link");if(t)r=t;else{var a=(i.body||i.getElementsByTagName("head")[0]).childNodes;r=a[a.length-1]}var f=i.styleSheets;if(o)for(var l in o)o.hasOwnProperty(l)&&d.setAttribute(l,o[l]);d.rel="stylesheet",d.href=e,d.media="only x",function e(t){if(i.body)return t();setTimeout(function(){e(t)})}(function(){r.parentNode.insertBefore(d,t?r:r.nextSibling)});var s=function(e){for(var t=d.href,n=f.length;n--;)if(f[n].href===t)return e();setTimeout(function(){s(e)})};function u(){d.addEventListener&&d.removeEventListener("load",u),d.media=n||"all"}return d.addEventListener&&d.addEventListener("load",u),(d.onloadcssdefined=s)(u),d}"undefined"!=typeof exports?exports.loadCSS=e:c.loadCSS=e}("undefined"!=typeof global?global:this);</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//loadCSS('./css/foundation-lib.css')
|
||||||
|
loadCSS('./css/vendor.css')
|
||||||
|
//loadCSS('./css/style.css')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/foundation-lib.css">
|
||||||
|
{#<link rel="stylesheet" href="css/vendor.css">#}
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
{% block bodyBegin %}
|
||||||
|
<body class="{{ body_class }} ">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="l-wrapper">
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
{% include "layouts/_header.twig" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
<main class="l-main" id="main">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
{% include "layouts/_footer.twig" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% block modal %}
|
||||||
|
{% include 'layouts/_modal.twig' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
|
||||||
|
{#<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDGS_V5ZeY_F7e3PhrLcs8U2vGYV_e637U"></script>#}
|
||||||
|
{% block extraJsLib %}{% endblock %}
|
||||||
|
<script type="text/javascript" src="js/libs.js"></script>
|
||||||
|
<script type="text/javascript" src="js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
112
webpunk-templates/src/twig/layouts/_modal.twig
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
<!-- Modals -->
|
||||||
|
|
||||||
|
<!-- authStart-->
|
||||||
|
<div id="authStart" class="b-modal b-modal--form b-modal--dark b-modal--wide" style="display: none">
|
||||||
|
<div class="b-modal__bg"></div>
|
||||||
|
<div class="b-modal__body">
|
||||||
|
<a data-gtm="app_popup_close" class="b-modal__close btn-close" href="#">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32.828" height="32.828" viewBox="0 0 32.828 32.828">
|
||||||
|
<g id="close" transform="translate(-7.586 -7.586)">
|
||||||
|
<path id="Контур_8334" data-name="Контур 8334" d="M9,9,39,39" transform="translate(0 0)" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" />
|
||||||
|
<path id="Контур_8335" data-name="Контур 8335" d="M39,9,9,39" transform="translate(0.001 0)" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="size-em bg-el bg-el--left show-for-tablet">
|
||||||
|
<picture class="img-render">
|
||||||
|
<source srcset="./img/bg/reg_coin.webp" type="image/webp">
|
||||||
|
<img loading="lazy" class="img-adaptive --em" width="195" src="./img/bg/reg_coin.png" alt="pic">
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
<div class="size-em bg-el bg-el--right show-for-tablet">
|
||||||
|
<picture class="img-render">
|
||||||
|
<source srcset="./img/bg/reg_ovi.webp" type="image/webp">
|
||||||
|
<img loading="lazy" class="img-adaptive --em" width="192" src="./img/bg/reg_ovi.png" alt="pic">
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="b-modal__form">
|
||||||
|
<div class="b-modal__content">
|
||||||
|
<div class="s-hero__section-head text-center">
|
||||||
|
<div class="section-title mb-0">
|
||||||
|
Для участия в акции <br>войди в свой аккаунт
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="b-modal__btn">
|
||||||
|
<a class="btn m-large m-white" target="_blank" href="https://fon.bet/bonuses?promoId=INFO_RU_CRM_ACT_OVI_REC">Войди с FONBET</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="s-hero__section-form pt-4">
|
||||||
|
<div class="b-reg text-center">
|
||||||
|
<div class="b-reg__txt">
|
||||||
|
Зарегистрироваться <br class="hide-for-tablet">и получить фрибет <br>
|
||||||
|
<span class="bold _large">до 15 000 р</span><br>
|
||||||
|
без депозита.
|
||||||
|
</div>
|
||||||
|
<div class="b-reg__btn">
|
||||||
|
<a class="btn m-large btn-gradient m-red" target="_blank" href="https://fon.bet/?deepLink=start_auth_process&promoAlias=thegr8chase_rekord">ЗАРЕГИСТРИРОВАТЬСЯ</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="b-modal__pic fullwidth text-center hide-for-tablet">
|
||||||
|
<div class="size-em bg-el bg-el--coin">
|
||||||
|
<picture class="img-render">
|
||||||
|
<source srcset="./img/bg/reg_coin.webp" type="image/webp">
|
||||||
|
<img loading="lazy" class="img-adaptive --em" width="195" src="./img/bg/reg_coin.png" alt="pic">
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
<picture class="img-render img-ovi">
|
||||||
|
<source srcset="./img/bg/reg_ovi_m.webp" type="image/webp">
|
||||||
|
<img loading="lazy" class="" width="256" src="./img/bg/reg_ovi_m.png" alt="pic">
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="formSuccess" class="b-modal b-modal--form b-modal--popup b-modal--wide" style="display: none">
|
||||||
|
<div style="pointer-events: none" class="b-modal__bg"></div>
|
||||||
|
<div class="b-modal__body">
|
||||||
|
<a data-gtm="app_popup_close" class="b-modal__close btn-close" href="http://fon.bet/promo/thegr8chase/">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32.828" height="32.828" viewBox="0 0 32.828 32.828">
|
||||||
|
<g id="close" transform="translate(-7.586 -7.586)">
|
||||||
|
<path id="Контур_8334" data-name="Контур 8334" d="M9,9,39,39" transform="translate(0 0)" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" />
|
||||||
|
<path id="Контур_8335" data-name="Контур 8335" d="M39,9,9,39" transform="translate(0.001 0)" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="b-modal__form">
|
||||||
|
<div class="b-modal__content">
|
||||||
|
<div class="s-hero__section-head text-center">
|
||||||
|
<div class="s-openarena__title font2 _small text--primary mb-0">
|
||||||
|
ПОЧТИ <br class="hide-for-tablet">ГОТОВО!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="s-hero__section-form pt-2 pb-2 text-center">
|
||||||
|
<div class="s-openarena__txt">
|
||||||
|
Сделай ставку на любой вид спорта <br class="show-for-tablet">
|
||||||
|
в FONBET от 500 рублей, чтобы принять <br class="show-for-tablet">
|
||||||
|
участие в розыгрыше слотов на турнир. <br class="show-for-tablet">
|
||||||
|
Мы подведем результаты розыгрыша 15 июля!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="s-openarena__btn pt-3 text-center mt-auto mb-auto">
|
||||||
|
<a class="btn m-large" href="https://fon.bet">ЗАЛЕТАЙ В ИГРУ</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--// Modals -->
|
||||||
25
webpunk-templates/src/twig/pages/about.twig
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends "layouts/_main-lp.twig" %}
|
||||||
|
{% block bodyBegin %}
|
||||||
|
{% set body_class = 'has-transparent-header height-100% cera-pro no-recaptcha static-form-mob form-is-hide' %}
|
||||||
|
{{ parent() }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block preload %}
|
||||||
|
{# <link rel="preload" as="image" href="./img/bg/bg_popup_bar.png" media="(min-width:1024px)">#}
|
||||||
|
{# <link rel="preload" as="image" href="./img/bg/bg_popup_bar_mob.jpg" media="(max-width:1023px)">#}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}{% endblock %}
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
{% block modal %}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block extraJS %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<!------->
|
||||||
|
{% include 'partial/_hero.twig' %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
25
webpunk-templates/src/twig/pages/index.twig
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends "layouts/_main-lp.twig" %}
|
||||||
|
{% block bodyBegin %}
|
||||||
|
{% set body_class = 'has-transparent-header height-100% cera-pro no-recaptcha static-form-mob form-is-hide' %}
|
||||||
|
{{ parent() }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block preload %}
|
||||||
|
{# <link rel="preload" as="image" href="./img/bg/bg_popup_bar.png" media="(min-width:1024px)">#}
|
||||||
|
{# <link rel="preload" as="image" href="./img/bg/bg_popup_bar_mob.jpg" media="(max-width:1023px)">#}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}{% endblock %}
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
{# {% block modal %}{% endblock %} #}
|
||||||
|
|
||||||
|
|
||||||
|
{% block extraJS %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<!------->
|
||||||
|
{% include 'partial/_hero.twig' %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
17
webpunk-templates/src/twig/partial/_hero.twig
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{% set image_url = './img/owl.jpg' %}
|
||||||
|
{% set image_alt = 'A descriptive alt text for the image' %}
|
||||||
|
{% set title = 'Welcome to Our Website' %}
|
||||||
|
{% set subtitle = 'Discover amazing content and features' %}
|
||||||
|
{% set cta_text = 'Get Started' %}
|
||||||
|
{% set cta_link = 'https://example.com/get-started' %}
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<div class="hero__content">
|
||||||
|
<h1 class="hero__title">{{ title }}</h1>
|
||||||
|
<p class="hero__subtitle">{{ subtitle }}</p>
|
||||||
|
<a href="{{ cta_link }}" class="hero__cta">{{ cta_text }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="hero__image">
|
||||||
|
<img width="500" src="{{ image_url }}" alt="{{ image_alt }}">
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
46
webpunk-templates/src/urls.json
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"common": [
|
||||||
|
"//clientsapi01w.bk6bba-resources.com",
|
||||||
|
"//clientsapi02w.bk6bba-resources.com",
|
||||||
|
"//clientsapi03w.bk6bba-resources.com",
|
||||||
|
"//clientsapi04w.bk6bba-resources.com",
|
||||||
|
"//clientsapi05w.bk6bba-resources.com",
|
||||||
|
"//clientsapi06w.bk6bba-resources.com",
|
||||||
|
"//clientsapi31w.bk6bba-resources.com",
|
||||||
|
"//clientsapi51w.bk6bba-resources.com",
|
||||||
|
"//clientsapi52w.bk6bba-resources.com"
|
||||||
|
],
|
||||||
|
"line": [
|
||||||
|
"//line01w.bk6bba-resources.com",
|
||||||
|
"//line02w.bk6bba-resources.com",
|
||||||
|
"//line03w.bk6bba-resources.com",
|
||||||
|
"//line04w.bk6bba-resources.com",
|
||||||
|
"//line05w.bk6bba-resources.com",
|
||||||
|
"//line06w.bk6bba-resources.com",
|
||||||
|
"//line07w.bk6bba-resources.com",
|
||||||
|
"//line08w.bk6bba-resources.com",
|
||||||
|
"//line31w.bk6bba-resources.com",
|
||||||
|
"//line32w.bk6bba-resources.com",
|
||||||
|
"//line51w.bk6bba-resources.com",
|
||||||
|
"//line52w.bk6bba-resources.com",
|
||||||
|
"//line53w.bk6bba-resources.com",
|
||||||
|
"//line54w.bk6bba-resources.com",
|
||||||
|
"//line55w.bk6bba-resources.com"
|
||||||
|
],
|
||||||
|
"lineByScopeMarket": {},
|
||||||
|
"static": "//origin.bk6bba-resources.com",
|
||||||
|
"commonApi": "//fastviewdata.bk6bba-resources.com",
|
||||||
|
"staticSiteDir": "/webStaticRed/website",
|
||||||
|
"mapOfAdditionalOriginURIs": {},
|
||||||
|
"updateUtcDate": "Fri, 06 Dec 2024 13:39:24 GMT",
|
||||||
|
"site": {
|
||||||
|
"version": "1.43.56",
|
||||||
|
"forceUpdateVersion": 0,
|
||||||
|
"environment": "production",
|
||||||
|
"ref": "bWFzdGVy",
|
||||||
|
"configHash": "a4f175658adc16c9d8586b78dba20207"
|
||||||
|
},
|
||||||
|
"enableClickStream": true,
|
||||||
|
"defaultFracSize": 0,
|
||||||
|
"betRoundAccuracy": 1
|
||||||
|
}
|
||||||
240
webpunk-templates/ssl-manager.js
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { execSync } from 'child_process'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = path.dirname(__filename)
|
||||||
|
|
||||||
|
const sslDir = path.join(__dirname, 'ssl')
|
||||||
|
const keyPath = path.join(sslDir, 'server.key')
|
||||||
|
const certPath = path.join(sslDir, 'server.crt')
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
reset: '\x1b[0m',
|
||||||
|
red: '\x1b[31m',
|
||||||
|
green: '\x1b[32m',
|
||||||
|
yellow: '\x1b[33m',
|
||||||
|
blue: '\x1b[34m',
|
||||||
|
magenta: '\x1b[35m',
|
||||||
|
cyan: '\x1b[36m'
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = (message, color = 'reset') => {
|
||||||
|
console.log(`${colors[color]}${message}${colors.reset}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkOpenSSL = () => {
|
||||||
|
try {
|
||||||
|
execSync('openssl version', { stdio: 'pipe' })
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const installInstructions = () => {
|
||||||
|
log('\n📋 Инструкции по установке OpenSSL:', 'cyan')
|
||||||
|
log('Windows: https://slproweb.com/products/Win32OpenSSL.html', 'blue')
|
||||||
|
log('macOS: brew install openssl', 'blue')
|
||||||
|
log('Ubuntu/Debian: sudo apt-get install openssl', 'blue')
|
||||||
|
log('CentOS/RHEL: sudo yum install openssl', 'blue')
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCertificates = (domain = 'localhost', days = 365) => {
|
||||||
|
if (!fs.existsSync(sslDir)) {
|
||||||
|
fs.mkdirSync(sslDir, { recursive: true })
|
||||||
|
log(`📁 Создана папка: ${sslDir}`, 'green')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
log('🔐 Создание SSL сертификатов...', 'yellow')
|
||||||
|
|
||||||
|
// Генерируем приватный ключ
|
||||||
|
log('📝 Генерация приватного ключа...', 'blue')
|
||||||
|
execSync(`openssl genrsa -out "${keyPath}" 2048`, { stdio: 'pipe' })
|
||||||
|
|
||||||
|
// Конфигурация для сертификата с SAN
|
||||||
|
const configContent = `
|
||||||
|
[req]
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
req_extensions = v3_req
|
||||||
|
prompt = no
|
||||||
|
|
||||||
|
[req_distinguished_name]
|
||||||
|
C = RU
|
||||||
|
ST = Development
|
||||||
|
L = Local
|
||||||
|
O = Development Server
|
||||||
|
OU = IT Department
|
||||||
|
CN = ${domain}
|
||||||
|
|
||||||
|
[v3_req]
|
||||||
|
keyUsage = keyEncipherment, dataEncipherment
|
||||||
|
extendedKeyUsage = serverAuth
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = ${domain}
|
||||||
|
DNS.2 = *.${domain}
|
||||||
|
DNS.3 = localhost
|
||||||
|
DNS.4 = *.localhost
|
||||||
|
IP.1 = 127.0.0.1
|
||||||
|
IP.2 = ::1
|
||||||
|
`
|
||||||
|
|
||||||
|
const configPath = path.join(sslDir, 'openssl.conf')
|
||||||
|
fs.writeFileSync(configPath, configContent.trim())
|
||||||
|
|
||||||
|
// Генерируем сертификат
|
||||||
|
log('📜 Генерация сертификата...', 'blue')
|
||||||
|
const certCommand = `openssl req -new -x509 -key "${keyPath}" -out "${certPath}" -days ${days} -config "${configPath}" -extensions v3_req`
|
||||||
|
execSync(certCommand, { stdio: 'pipe' })
|
||||||
|
|
||||||
|
// Удаляем временный конфиг
|
||||||
|
fs.unlinkSync(configPath)
|
||||||
|
|
||||||
|
log('\n✅ SSL сертификаты успешно созданы!', 'green')
|
||||||
|
log(`🔑 Ключ: ${keyPath}`, 'cyan')
|
||||||
|
log(`📄 Сертификат: ${certPath}`, 'cyan')
|
||||||
|
log(`🌐 Домен: ${domain}`, 'cyan')
|
||||||
|
log(`⏰ Действителен: ${days} дней`, 'cyan')
|
||||||
|
|
||||||
|
// Показываем информацию о сертификате
|
||||||
|
showCertInfo()
|
||||||
|
|
||||||
|
log('\n⚠️ Важно:', 'yellow')
|
||||||
|
log('• Это сертификаты только для разработки', 'yellow')
|
||||||
|
log('• Добавьте сертификат в доверенные в браузере для устранения предупреждений', 'yellow')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log('\n❌ Ошибка создания SSL сертификатов:', 'red')
|
||||||
|
log(error.message, 'red')
|
||||||
|
installInstructions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const showCertInfo = () => {
|
||||||
|
if (!fs.existsSync(certPath)) {
|
||||||
|
log('❌ Сертификат не найден', 'red')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
log('\n📋 Информация о сертификате:', 'cyan')
|
||||||
|
const certInfo = execSync(`openssl x509 -in "${certPath}" -text -noout`, { encoding: 'utf8' })
|
||||||
|
|
||||||
|
// Извлекаем нужную информацию
|
||||||
|
const subjectMatch = certInfo.match(/Subject: (.+)/);
|
||||||
|
const validFromMatch = certInfo.match(/Not Before: (.+)/);
|
||||||
|
const validToMatch = certInfo.match(/Not After : (.+)/);
|
||||||
|
const sanMatch = certInfo.match(/DNS:([^,\n]+)/g);
|
||||||
|
|
||||||
|
if (subjectMatch) log(`📝 Subject: ${subjectMatch[1].trim()}`, 'blue')
|
||||||
|
if (validFromMatch) log(`📅 Действителен с: ${validFromMatch[1].trim()}`, 'blue')
|
||||||
|
if (validToMatch) log(`📅 Действителен до: ${validToMatch[1].trim()}`, 'blue')
|
||||||
|
if (sanMatch) {
|
||||||
|
log(`🌐 Домены: ${sanMatch.map(d => d.replace('DNS:', '')).join(', ')}`, 'blue')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log('❌ Ошибка чтения информации о сертификате', 'red')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCertificates = () => {
|
||||||
|
let deleted = false
|
||||||
|
|
||||||
|
if (fs.existsSync(keyPath)) {
|
||||||
|
fs.unlinkSync(keyPath)
|
||||||
|
log(`🗑️ Удален ключ: ${keyPath}`, 'yellow')
|
||||||
|
deleted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(certPath)) {
|
||||||
|
fs.unlinkSync(certPath)
|
||||||
|
log(`🗑️ Удален сертификат: ${certPath}`, 'yellow')
|
||||||
|
deleted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleted) {
|
||||||
|
log('✅ SSL сертификаты удалены', 'green')
|
||||||
|
} else {
|
||||||
|
log('ℹ️ SSL сертификаты не найдены', 'blue')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkCertificates = () => {
|
||||||
|
const keyExists = fs.existsSync(keyPath)
|
||||||
|
const certExists = fs.existsSync(certPath)
|
||||||
|
|
||||||
|
log('\n🔍 Проверка SSL сертификатов:', 'cyan')
|
||||||
|
log(`🔑 Ключ: ${keyExists ? '✅ Существует' : '❌ Не найден'}`, keyExists ? 'green' : 'red')
|
||||||
|
log(`📄 Сертификат: ${certExists ? '✅ Существует' : '❌ Не найден'}`, certExists ? 'green' : 'red')
|
||||||
|
|
||||||
|
if (keyExists && certExists) {
|
||||||
|
showCertInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyExists && certExists
|
||||||
|
}
|
||||||
|
|
||||||
|
const showHelp = () => {
|
||||||
|
log('\n🔧 SSL Manager - Утилита для управления SSL сертификатами\n', 'cyan')
|
||||||
|
log('Использование:', 'yellow')
|
||||||
|
log(' node ssl-manager.js <команда> [опции]\n', 'blue')
|
||||||
|
log('Команды:', 'yellow')
|
||||||
|
log(' create [domain] [days] Создать новые сертификаты (по умолчанию: localhost, 365 дней)', 'blue')
|
||||||
|
log(' check Проверить существующие сертификаты', 'blue')
|
||||||
|
log(' info Показать информацию о сертификате', 'blue')
|
||||||
|
log(' delete Удалить существующие сертификаты', 'blue')
|
||||||
|
log(' help Показать эту справку\n', 'blue')
|
||||||
|
log('Примеры:', 'yellow')
|
||||||
|
log(' node ssl-manager.js create', 'green')
|
||||||
|
log(' node ssl-manager.js create mysite.local 30', 'green')
|
||||||
|
log(' node ssl-manager.js check', 'green')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Основная логика
|
||||||
|
const command = process.argv[2]
|
||||||
|
const arg1 = process.argv[3]
|
||||||
|
const arg2 = process.argv[4]
|
||||||
|
|
||||||
|
// Проверяем наличие OpenSSL
|
||||||
|
if (!checkOpenSSL() && command !== 'help' && command !== 'check') {
|
||||||
|
log('❌ OpenSSL не найден в системе', 'red')
|
||||||
|
installInstructions()
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case 'create':
|
||||||
|
const domain = arg1 || 'localhost'
|
||||||
|
const days = parseInt(arg2) || 365
|
||||||
|
createCertificates(domain, days)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'check':
|
||||||
|
checkCertificates()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'info':
|
||||||
|
showCertInfo()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'delete':
|
||||||
|
deleteCertificates()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'help':
|
||||||
|
case undefined:
|
||||||
|
showHelp()
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
log(`❌ Неизвестная команда: ${command}`, 'red')
|
||||||
|
showHelp()
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||