Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f53be810f | ||
|
|
29a063c806 | ||
|
|
d16a5459ad | ||
|
|
ddb0f9ebbb | ||
|
|
b86bb6c883 | ||
|
|
110c525e40 | ||
|
|
9936be03ad | ||
|
|
afb75b7cf7 | ||
|
|
abd4678e4a |
@@ -583,6 +583,7 @@ CLI больше не нужен.
|
|||||||
| `.env.sample` | Env template |
|
| `.env.sample` | Env template |
|
||||||
| `Dockerfile` | Сборка |
|
| `Dockerfile` | Сборка |
|
||||||
| `docker-compose.local.yml` | Dev |
|
| `docker-compose.local.yml` | Dev |
|
||||||
|
| `docker-compose.prod-test.yml` | Тестовый Prod |
|
||||||
| `docker-compose.prod.yml` | Prod |
|
| `docker-compose.prod.yml` | Prod |
|
||||||
| `config/nginx/hypn0-app--external-nginx.conf` | Nginx |
|
| `config/nginx/hypn0-app--external-nginx.conf` | Nginx |
|
||||||
| `.gitea/workflows/docker-publish.yaml` | CI/CD |
|
| `.gitea/workflows/docker-publish.yaml` | CI/CD |
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ server {
|
|||||||
charset utf-8;
|
charset utf-8;
|
||||||
client_max_body_size 10M; # Разрешаем загрузку не слишком больших картинок
|
client_max_body_size 10M; # Разрешаем загрузку не слишком больших картинок
|
||||||
|
|
||||||
|
# --- СЕТЕВЫЕ ОПТИМИЗАЦИИ И ЗАЩИТА ОТ ТАЙМАУТОВ ЧЕРЕЗ VPN (MTU/MSS) ---
|
||||||
|
tcp_nopush on; # Отправляет заголовки и начало файла в одном TCP-пакете
|
||||||
|
tcp_nodelay on; # Отключает задержку Nagle для быстрых интерактивных ответов
|
||||||
|
ssl_buffer_size 4k; # Уменьшенный буфер TLS: решает зависания пакетов в VPN/WireGuard туннелях
|
||||||
|
|
||||||
# Логи (пути могут отличаться в зависимости от настроек сервера, здесь стандартные для Ubuntu)
|
# Логи (пути могут отличаться в зависимости от настроек сервера, здесь стандартные для Ubuntu)
|
||||||
access_log /var/log/nginx/hypn0.access.log;
|
access_log /var/log/nginx/hypn0.access.log;
|
||||||
error_log /var/log/nginx/hypn0.error.log;
|
error_log /var/log/nginx/hypn0.error.log;
|
||||||
@@ -49,7 +54,7 @@ server {
|
|||||||
gzip_vary on; # Добавляет заголовок Vary: Accept-Encoding
|
gzip_vary on; # Добавляет заголовок Vary: Accept-Encoding
|
||||||
gzip_proxied any; # Сжимать ответы, даже если мы за прокси
|
gzip_proxied any; # Сжимать ответы, даже если мы за прокси
|
||||||
gzip_comp_level 6; # Оптимальный баланс скорость/сжатие
|
gzip_comp_level 6; # Оптимальный баланс скорость/сжатие
|
||||||
gzip_min_length 1000; # Не сжимать совсем мелочь
|
gzip_min_length 256; # Сжимать ответы от 256 байт
|
||||||
# Типы файлов для сжатия (HTML сжимается автоматически, его писать не нужно)
|
# Типы файлов для сжатия (HTML сжимается автоматически, его писать не нужно)
|
||||||
gzip_types
|
gzip_types
|
||||||
text/plain
|
text/plain
|
||||||
|
|||||||
+21
-10
@@ -5,18 +5,18 @@
|
|||||||
|
|
||||||
## Что здесь лежит
|
## Что здесь лежит
|
||||||
|
|
||||||
### `tailwind/` — сборка Tailwind CSS v3.4
|
### `tailwind/` — сборка Tailwind CSS v4
|
||||||
|
|
||||||
```
|
```
|
||||||
tailwind/
|
tailwind/
|
||||||
├── package.json # Зависимости: tailwindcss@3.4, postcss, autoprefixer
|
├── package.json # Зависимости: @tailwindcss/cli, tailwindcss v4
|
||||||
├── package-lock.json # Фиксация версий
|
├── package-lock.json # Фиксация версий
|
||||||
└── build-tailwind.sh # ← запускается из корня проекта
|
└── build-tailwind.sh # ← запускается из корня проекта (scripts/build-tailwind.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Что делает:**
|
**Что делает:**
|
||||||
- `build-tailwind.sh` создаёт временные файлы (`tailwind.config.js`, `postcss.config.js`, `input.css`),
|
- `build-tailwind.sh` создаёт временный `input.css` с директивами `@import "tailwindcss";`, `@source` и импортом `tailwind-custom.css`,
|
||||||
запускает `npm ci` + `npm run build`, собирает `public/static/css/tailwind.min.css`,
|
запускает `npm install` + `npm run build` (`@tailwindcss/cli`), собирает `public/static/css/tailwind.min.css`,
|
||||||
затем удаляет временные файлы.
|
затем удаляет временные файлы.
|
||||||
- Результат: `public/static/css/tailwind.min.css` — минифицированный CSS со всеми
|
- Результат: `public/static/css/tailwind.min.css` — минифицированный CSS со всеми
|
||||||
используемыми утилитами + кастомные стили из `hypn0/templates/css/tailwind-custom.css`.
|
используемыми утилитами + кастомные стили из `hypn0/templates/css/tailwind-custom.css`.
|
||||||
@@ -37,6 +37,18 @@ alpine/
|
|||||||
└── package-lock.json
|
└── package-lock.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `codemirror/` — сборка CodeMirror 6 для админки Django
|
||||||
|
|
||||||
|
```
|
||||||
|
codemirror/
|
||||||
|
├── package.json # Зависимости: @codemirror/*, esbuild, @uiw/codemirror-theme-solarized
|
||||||
|
└── package-lock.json # Фиксация версий
|
||||||
|
```
|
||||||
|
|
||||||
|
**Что делает:**
|
||||||
|
- `build-codemirror.sh` генерирует точку входа `src/editor.js` с поддержкой языков (HTML, CSS, JavaScript, JSON), автоматической смены темы (Solarized Light / Dark), форматирования и двусторонней синхронизации с Django админкой (`textarea[data-codemirror-editor]`).
|
||||||
|
- Собирает единый минифицированный IIFE-бандл через `esbuild` в `public/static/codemirror/editor.js`.
|
||||||
|
|
||||||
## Как запускать сборки
|
## Как запускать сборки
|
||||||
|
|
||||||
Из корня проекта:
|
Из корня проекта:
|
||||||
@@ -50,18 +62,17 @@ bash scripts/build-htmx.sh
|
|||||||
|
|
||||||
# Alpine.js
|
# Alpine.js
|
||||||
bash scripts/build-alpine.sh
|
bash scripts/build-alpine.sh
|
||||||
|
|
||||||
|
# CodeMirror 6 (для админки Django)
|
||||||
|
bash scripts/build-codemirror.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Каждый скрипт сам делает всё:
|
Каждый скрипт сам делает всё:
|
||||||
1. проверяет наличие `npm`;
|
1. проверяет наличие `npm`;
|
||||||
2. создаёт временные файлы;
|
2. создаёт временные файлы;
|
||||||
3. устанавливает зависимости через `npm ci`;
|
3. устанавливает зависимости через `npm ci` (или `npm install`);
|
||||||
4. запускает `npm run build`;
|
4. запускает `npm run build`;
|
||||||
5. кладёт готовый бандл в `public/static/...`;
|
5. кладёт готовый бандл в `public/static/...`;
|
||||||
6. удаляет временные файлы.
|
6. удаляет временные файлы.
|
||||||
|
|
||||||
В рабочем дереве не остаётся мусора от сборки.
|
В рабочем дереве не остаётся мусора от сборки.
|
||||||
|
|
||||||
## Кодовая статика
|
|
||||||
|
|
||||||
В будущем — возможно, сборка CodeMirror 6 для админки Django.
|
|
||||||
|
|||||||
+1372
-1468
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,7 @@
|
|||||||
"build": "tailwindcss -i input.css -o ../../public/static/css/tailwind.min.css --minify"
|
"build": "tailwindcss -i input.css -o ../../public/static/css/tailwind.min.css --minify"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tailwindcss": "^3.4.0",
|
"@tailwindcss/cli": "^4.0.0",
|
||||||
"postcss": "^8.4.35",
|
"tailwindcss": "^4.0.0"
|
||||||
"autoprefixer": "^10.4.17"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,6 +146,8 @@ val=>{if(val)document.documentElement.classList.add('dark');else document.docume
|
|||||||
{% block CONTENT %}{% endblock CONTENT %}
|
{% block CONTENT %}{% endblock CONTENT %}
|
||||||
</main>
|
</main>
|
||||||
{# {% include "blocks/footer.jinja2" %} #}{% endblock BODY %}{% if not ALLOW_TRACKING %}{# Если клиент еще не согласился на отслеживание покажем ему уведомление #}{% include "block/allow-tracking.html" %}{% else %}{# Если согласился -- отслеживаем#}
|
{# {% include "blocks/footer.jinja2" %} #}{% endblock BODY %}{% if not ALLOW_TRACKING %}{# Если клиент еще не согласился на отслеживание покажем ему уведомление #}{% include "block/allow-tracking.html" %}{% else %}{# Если согласился -- отслеживаем#}
|
||||||
<script src="{% static 'js/analytics.js' %}"></script>{% endif %}
|
<script src="{% static 'js/analytics.js' %}"></script>
|
||||||
|
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5FVH3KMJ" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||||
|
<noscript><div><img src="https://mc.yandex.ru/watch/112812760" style="position:absolute; left:-9999px;" alt="" /></div></noscript>{% endif %}
|
||||||
{% block ADD_JS1 %}{% endblock ADD_JS1 %}{% block ADD_JS2 %}{% endblock ADD_JS2 %}{% block ADD_JS3 %}{% endblock ADD_JS3 %}</body>
|
{% block ADD_JS1 %}{% endblock ADD_JS1 %}{% block ADD_JS2 %}{% endblock ADD_JS2 %}{% block ADD_JS3 %}{% endblock ADD_JS3 %}</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -6,8 +6,8 @@
|
|||||||
<title>400 — Сбой ментального протокола | HypnoSVG</title>
|
<title>400 — Сбой ментального протокола | HypnoSVG</title>
|
||||||
<meta name="description" content="Ошибка 400: Некорректный запрос к матрице HypnoSVG. Сервер не распознал структуру переданного сигнала." />
|
<meta name="description" content="Ошибка 400: Некорректный запрос к матрице HypnoSVG. Сервер не распознал структуру переданного сигнала." />
|
||||||
<meta name="robots" content="noindex, follow" />
|
<meta name="robots" content="noindex, follow" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/media/_error/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var saved = localStorage.getItem('darkMode');
|
var saved = localStorage.getItem('darkMode');
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<title>401 — Требуется ментальная идентификация | HypnoSVG</title>
|
<title>401 — Требуется ментальная идентификация | HypnoSVG</title>
|
||||||
<meta name="description" content="Ошибка 401: Доступ к закрытому узлу HypnoSVG требует прохождения авторизации." />
|
<meta name="description" content="Ошибка 401: Доступ к закрытому узлу HypnoSVG требует прохождения авторизации." />
|
||||||
<meta name="robots" content="noindex, follow" />
|
<meta name="robots" content="noindex, follow" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/media/_error/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var saved = localStorage.getItem('darkMode');
|
var saved = localStorage.getItem('darkMode');
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<title>403 — Отказано бюрократом 24-го уровня | HypnoSVG</title>
|
<title>403 — Отказано бюрократом 24-го уровня | HypnoSVG</title>
|
||||||
<meta name="description" content="Ошибка 403: Доступ к ресурсу категорически запрещен протоколами безопасности HypnoSVG." />
|
<meta name="description" content="Ошибка 403: Доступ к ресурсу категорически запрещен протоколами безопасности HypnoSVG." />
|
||||||
<meta name="robots" content="noindex, follow" />
|
<meta name="robots" content="noindex, follow" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/media/_error/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var saved = localStorage.getItem('darkMode');
|
var saved = localStorage.getItem('darkMode');
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<title>404 — Векторная реальность не найдена | HypnoSVG</title>
|
<title>404 — Векторная реальность не найдена | HypnoSVG</title>
|
||||||
<meta name="description" content="Ошибка 404: Запрашиваемая страница HypnoSVG не существует или стёрта волей Гипножабы." />
|
<meta name="description" content="Ошибка 404: Запрашиваемая страница HypnoSVG не существует или стёрта волей Гипножабы." />
|
||||||
<meta name="robots" content="noindex, follow" />
|
<meta name="robots" content="noindex, follow" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/media/_error/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var saved = localStorage.getItem('darkMode');
|
var saved = localStorage.getItem('darkMode');
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<title>413 — Перегрузка сенсорных каналов | HypnoSVG</title>
|
<title>413 — Перегрузка сенсорных каналов | HypnoSVG</title>
|
||||||
<meta name="description" content="Ошибка 413: Загружаемый файл превышает допустимый размер ментального буфера HypnoSVG." />
|
<meta name="description" content="Ошибка 413: Загружаемый файл превышает допустимый размер ментального буфера HypnoSVG." />
|
||||||
<meta name="robots" content="noindex, follow" />
|
<meta name="robots" content="noindex, follow" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/media/_error/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var saved = localStorage.getItem('darkMode');
|
var saved = localStorage.getItem('darkMode');
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<title>429 — Слишком много мыслей в секунду | HypnoSVG</title>
|
<title>429 — Слишком много мыслей в секунду | HypnoSVG</title>
|
||||||
<meta name="description" content="Ошибка 429: Превышен лимит запросов к генератору HypnoSVG. Сделайте ментальную паузу." />
|
<meta name="description" content="Ошибка 429: Превышен лимит запросов к генератору HypnoSVG. Сделайте ментальную паузу." />
|
||||||
<meta name="robots" content="noindex, follow" />
|
<meta name="robots" content="noindex, follow" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/media/_error/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var saved = localStorage.getItem('darkMode');
|
var saved = localStorage.getItem('darkMode');
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<title>500 — Критический перегрев неокортекса | HypnoSVG</title>
|
<title>500 — Критический перегрев неокортекса | HypnoSVG</title>
|
||||||
<meta name="description" content="Ошибка 500: Внутренний сбой сервера HypnoSVG. Ведутся ментальные восстановительные работы." />
|
<meta name="description" content="Ошибка 500: Внутренний сбой сервера HypnoSVG. Ведутся ментальные восстановительные работы." />
|
||||||
<meta name="robots" content="noindex, follow" />
|
<meta name="robots" content="noindex, follow" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/media/_error/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var saved = localStorage.getItem('darkMode');
|
var saved = localStorage.getItem('darkMode');
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<title>502 — Контейнер потерял сознание | HypnoSVG</title>
|
<title>502 — Контейнер потерял сознание | HypnoSVG</title>
|
||||||
<meta name="description" content="Ошибка 502: Прокси-сервер не получил ответа от бэкенда HypnoSVG." />
|
<meta name="description" content="Ошибка 502: Прокси-сервер не получил ответа от бэкенда HypnoSVG." />
|
||||||
<meta name="robots" content="noindex, follow" />
|
<meta name="robots" content="noindex, follow" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/media/_error/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var saved = localStorage.getItem('darkMode');
|
var saved = localStorage.getItem('darkMode');
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<title>503 — Плановый сеанс гипнотерапии | HypnoSVG</title>
|
<title>503 — Плановый сеанс гипнотерапии | HypnoSVG</title>
|
||||||
<meta name="description" content="Ошибка 503: Сервис HypnoSVG временно недоступен из-за технического обслуживания или пиковой нагрузки." />
|
<meta name="description" content="Ошибка 503: Сервис HypnoSVG временно недоступен из-за технического обслуживания или пиковой нагрузки." />
|
||||||
<meta name="robots" content="noindex, follow" />
|
<meta name="robots" content="noindex, follow" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/media/_error/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var saved = localStorage.getItem('darkMode');
|
var saved = localStorage.getItem('darkMode');
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<title>504 — Разрыв астральной связи | HypnoSVG</title>
|
<title>504 — Разрыв астральной связи | HypnoSVG</title>
|
||||||
<meta name="description" content="Ошибка 504: Превышено время ожидания ответа от вышестоящего сервера HypnoSVG." />
|
<meta name="description" content="Ошибка 504: Превышено время ожидания ответа от вышестоящего сервера HypnoSVG." />
|
||||||
<meta name="robots" content="noindex, follow" />
|
<meta name="robots" content="noindex, follow" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/media/_error/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var saved = localStorage.getItem('darkMode');
|
var saved = localStorage.getItem('darkMode');
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<title>Реконструкция матрицы | HypnoSVG</title>
|
<title>Реконструкция матрицы | HypnoSVG</title>
|
||||||
<meta name="description" content="Сектор временно закрыт на квантовую реконструкцию и модернизацию алгоритмов HypnoSVG." />
|
<meta name="description" content="Сектор временно закрыт на квантовую реконструкцию и модернизацию алгоритмов HypnoSVG." />
|
||||||
<meta name="robots" content="noindex, follow" />
|
<meta name="robots" content="noindex, follow" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/static/img/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/media/_error/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/media/_error/favicon.ico" />
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var saved = localStorage.getItem('darkMode');
|
var saved = localStorage.getItem('darkMode');
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
{# НИЖНИЙ POPUP О КУКУХ И ОТСЛЕЖИВАНИИ #}<div id="allow-tracking-popup" x-data="{ agreed: !!localStorage.getItem('hypn0_vid') }" x-show="!agreed" x-cloak class="sticky bottom-0 z-50 bg-gray-100/80 dark:bg-slate-800/70 backdrop-blur border-t border-gray-300 dark:border-gray-700">
|
{# НИЖНИЙ POPUP О КУКУХ И ОТСЛЕЖИВАНИИ #}<div id="allow-tracking-popup" x-data="{ agreed: !!localStorage.getItem('hypn0_vid') }" x-show="!agreed" x-cloak class="sticky bottom-0 z-50 bg-gray-100/95 dark:bg-slate-800/90 backdrop-blur border-t border-gray-300 dark:border-gray-700">
|
||||||
<div class="max-w-7xl mx-auto px-4 py-8 grid grid-cols-[auto_1fr] gap-x-4">
|
<div class="max-w-7xl mx-auto p-3 sm:p-4 md:px-4 md:py-8 flex flex-col md:grid md:grid-cols-[1fr_auto] gap-2.5 md:gap-x-4 items-center">
|
||||||
<p class="flex-col px-6 py-6">
|
<p class="p-0 md:px-6 md:py-6 text-xs sm:text-sm md:text-base leading-snug md:leading-normal">
|
||||||
|
<span class="md:hidden">
|
||||||
|
Внимание! Этот сайт использует файлы-присоски (cookies) и алгоритмы ментального мерцания в соответствии с 152-<abbr title="Федеральный закон">ФЗ</abbr>, <abbr title="General Data Protection Regulation">GDPR</abbr> и <a href="/blog/security-and-privacy-policy">политикой конфи­денциаль­ности</a>.
|
||||||
|
</span>
|
||||||
|
<span class="hidden md:inline">
|
||||||
Внимание! В соответствии с 152-<abbr title="Федеральный закон">ФЗ</abbr>
|
Внимание! В соответствии с 152-<abbr title="Федеральный закон">ФЗ</abbr>
|
||||||
и <abbr title="General Data Protection Regulation">GDPR</abbr> и протоколами Нового Нью-Йорка,
|
и <abbr title="General Data Protection Regulation">GDPR</abbr> и протоколами Нового Нью-Йорка,
|
||||||
этот сайт использует файлы-присоски (cookies). ВСЕ СЛАВЯТ ГИПНОЖАБУ. Ваши обезли­ченные данные
|
этот сайт использует файлы-присоски (cookies). ВСЕ СЛАВЯТ ГИПНОЖАБУ. Ваши обезли­ченные данные
|
||||||
считываются алгоритмами ментального мерцания. Оставаясь на гипно-платформе <strong>hypn0</strong>, вы добровольно
|
считываются алгоритмами ментального мерцания. Оставаясь на гипно-платформе <strong>hypn0</strong>, вы добровольно
|
||||||
впадаете в транс и принимаете условия нашей <a href="/blog/security-and-privacy-policy">политики
|
впадаете в транс и принимаете условия нашей <a href="/blog/security-and-privacy-policy">политики
|
||||||
конфи­денциаль­ности</a>. <strong>Не пытайтесь переключить вкладку!</strong>
|
конфи­денциаль­ности</a>. <strong>Не пытайтесь переключить вкладку!</strong>
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<button @click="const vid = crypto.randomUUID(); localStorage.setItem('hypn0_vid', vid); document.cookie = `hypn0_vid=${vid}; path=/; max-age=63072000; SameSite=Lax`; agreed = true" class="text-lg px-6 py-2 my-6 border-2 rounded-md bg-emerald-600 hover:bg-emerald-500 dark:bg-emerald-500 dark:hover:bg-emerald-400 text-white">Подчиниться Гипножабе!</button>
|
<button @click="const vid = crypto.randomUUID(); localStorage.setItem('hypn0_vid', vid); document.cookie = `hypn0_vid=${vid}; path=/; max-age=63072000; SameSite=Lax`; agreed = true" class="w-full md:w-auto text-xs sm:text-sm md:text-lg px-4 md:px-6 py-2 md:py-2 my-0 md:my-6 border md:border-2 rounded-md bg-emerald-600 hover:bg-emerald-500 dark:bg-emerald-500 dark:hover:bg-emerald-400 text-white font-bold whitespace-nowrap cursor-pointer transition-colors">Подчиниться Гипножабе!</button>
|
||||||
</div>
|
</div>
|
||||||
</div>{# / НИЖНИЙ POPUP О КУКУХ И ОТСЛЕЖИВАНИИ #}
|
</div>{# / НИЖНИЙ POPUP О КУКУХ И ОТСЛЕЖИВАНИИ #}
|
||||||
@@ -5,13 +5,13 @@
|
|||||||
<div class="max-w-7xl mx-auto px-2 grid grid-cols-[1fr_auto] gap-x-4">
|
<div class="max-w-7xl mx-auto px-2 grid grid-cols-[1fr_auto] gap-x-4">
|
||||||
{# ЛОГОТИП #}{% comment %}
|
{# ЛОГОТИП #}{% comment %}
|
||||||
border-0 — логотип оборачивается в ссылку без текста, отключаем типовое
|
border-0 — логотип оборачивается в ссылку без текста, отключаем типовое
|
||||||
пунктирное подчёркивание ссылки, которое иначе появится под картинкой.{% endcomment %}<div class="flex py-4">
|
пунктирное подчёркивание ссылки, которое иначе появится под картинкой.{% endcomment %}<div class="flex items-center py-2 md:py-4">
|
||||||
<a href="/" class="border-0" title="HypnoSVG (hypn0) — Главная"><img src="{% static 'img/logo-hypn0.svg' %}" alt="HypnoSVG (hypn0) — Генератор анимированных SVG-халфтонов" title="HypnoSVG (hypn0)"></a>
|
<a href="/" class="border-0 flex items-center" title="HypnoSVG (hypn0) — Главная"><img src="{% static 'img/logo-hypn0.svg' %}" alt="HypnoSVG (hypn0) — Генератор анимированных SVG-халфтонов" title="HypnoSVG (hypn0)" class="h-7 sm:h-8 md:h-12 w-auto"></a>
|
||||||
</div>{# /ЛОГОТИП #}
|
</div>{# /ЛОГОТИП #}
|
||||||
{# ПРАВОЕ МЕНЮ #}<menu class="flex items-center gap-x-4 justify-self-start text-xl font-semibold text-slate-900 dark:text-slate-100">
|
{# ПРАВОЕ МЕНЮ #}<menu class="flex items-center gap-x-2 md:gap-x-4 justify-self-start text-sm sm:text-base md:text-xl font-semibold text-slate-900 dark:text-slate-100">
|
||||||
<a href="{% url 'hypn0_site:gallery_archive' %}" class="px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg border-0 text-slate-900 dark:text-slate-100">галерея</a>
|
<a href="{% url 'hypn0_site:gallery_archive' %}" class="px-2.5 py-1 sm:px-4 sm:py-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg border-0 text-slate-900 dark:text-slate-100">галерея</a>
|
||||||
<a href="{% url 'hypn0_site:blog_feed' %}" class="px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg hidden md:block hyphens-none border-0 text-slate-900 dark:text-slate-100">блог</a>
|
<a href="{% url 'hypn0_site:blog_feed' %}" class="px-2.5 py-1 sm:px-4 sm:py-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg hidden md:block hyphens-none border-0 text-slate-900 dark:text-slate-100">блог</a>
|
||||||
<a href="#" class="px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg hidden lg:block border-0 text-slate-900 dark:text-slate-100" @click.prevent="darkMode = !darkMode; localStorage.setItem('darkMode', darkMode)" title="Переключить тему"><span x-text="darkMode?'●':'○'"></span></a>
|
<a href="#" class="px-2.5 py-1 sm:px-4 sm:py-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg hidden lg:block border-0 text-slate-900 dark:text-slate-100" @click.prevent="darkMode = !darkMode; localStorage.setItem('darkMode', darkMode)" title="Светлая/Темная тема"><span x-text="darkMode?'●':'○'"></span></a>
|
||||||
</menu>{# /ПРАВОЕ МЕНЮ #}
|
</menu>{# /ПРАВОЕ МЕНЮ #}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -41,7 +41,8 @@
|
|||||||
<input type="hidden" name="color" value="{% if primary_color %}{{ primary_color }}{% else %}#a855ff{% endif %}">
|
<input type="hidden" name="color" value="{% if primary_color %}{{ primary_color }}{% else %}#a855ff{% endif %}">
|
||||||
|
|
||||||
<button id="like-to-gallery" type="submit" title="Отправить в галерею"
|
<button id="like-to-gallery" type="submit" title="Отправить в галерею"
|
||||||
:disabled="isPublishing"
|
:disabled="isPublishing"{% if ALLOW_TRACKING %}
|
||||||
|
{# Отслеживание Яндекс #}@click="window.ym && ym(112812760, 'reachGoal', 'like_art')"{% endif %}
|
||||||
class="group flex items-center gap-2 bg-emerald-600/90 hover:bg-emerald-600 text-white text-xs font-bold uppercase tracking-wide backdrop-blur-md px-4 py-2.5 rounded-full shadow-xl transition-all hover:scale-105 active:scale-95 disabled:opacity-60 disabled:scale-100 disabled:pointer-events-none cursor-pointer">
|
class="group flex items-center gap-2 bg-emerald-600/90 hover:bg-emerald-600 text-white text-xs font-bold uppercase tracking-wide backdrop-blur-md px-4 py-2.5 rounded-full shadow-xl transition-all hover:scale-105 active:scale-95 disabled:opacity-60 disabled:scale-100 disabled:pointer-events-none cursor-pointer">
|
||||||
<template x-if="isPublishing">
|
<template x-if="isPublishing">
|
||||||
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
|
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
class="inline-flex items-center">
|
class="inline-flex items-center">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
{% if user_voted %}disabled{% endif %}
|
{% if user_voted %}disabled{% endif %}{% if ALLOW_TRACKING %}
|
||||||
|
{# Отслеживание Яндекс #}@click="window.ym && ym(112812760, 'reachGoal', 'like_art')"{% endif %}
|
||||||
class="flex items-center gap-1.5 px-3.5 py-1.5 rounded-full text-xs font-bold transition-all {% if user_voted %}bg-rose-500/20 text-rose-400 border border-rose-500/30 cursor-default{% else %}bg-stone-200 dark:bg-stone-800 hover:bg-rose-500 hover:text-white text-stone-700 dark:text-stone-300 cursor-pointer hover:scale-105 active:scale-95 shadow-sm{% endif %}">
|
class="flex items-center gap-1.5 px-3.5 py-1.5 rounded-full text-xs font-bold transition-all {% if user_voted %}bg-rose-500/20 text-rose-400 border border-rose-500/30 cursor-default{% else %}bg-stone-200 dark:bg-stone-800 hover:bg-rose-500 hover:text-white text-stone-700 dark:text-stone-300 cursor-pointer hover:scale-105 active:scale-95 shadow-sm{% endif %}">
|
||||||
<svg class="w-4 h-4 {% if user_voted %}fill-rose-500 text-rose-500{% else %}fill-none stroke-current{% endif %}" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
<svg class="w-4 h-4 {% if user_voted %}fill-rose-500 text-rose-500{% else %}fill-none stroke-current{% endif %}" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/>
|
||||||
|
|||||||
@@ -123,7 +123,7 @@
|
|||||||
<div class="flex flex-wrap items-center gap-2.5">
|
<div class="flex flex-wrap items-center gap-2.5">
|
||||||
<!-- Кнопка Копировать SVG -->
|
<!-- Кнопка Копировать SVG -->
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@click="navigator.clipboard.writeText(document.getElementById('raw-svg-source').value); copiedSvg = true; setTimeout(() => copiedSvg = false, 2500)"
|
@click="navigator.clipboard.writeText(document.getElementById('raw-svg-source').value); copiedSvg = true; setTimeout(() => copiedSvg = false, 2500){% if ALLOW_TRACKING %}; window.ym && ym(112812760, 'reachGoal', 'copy_svg'){% endif %}"
|
||||||
class="flex items-center gap-2 px-4 py-2 bg-stone-100 hover:bg-stone-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-stone-800 dark:text-stone-200 rounded-xl text-xs font-bold transition-all hover:scale-105 active:scale-95 shadow-sm cursor-pointer">
|
class="flex items-center gap-2 px-4 py-2 bg-stone-100 hover:bg-stone-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-stone-800 dark:text-stone-200 rounded-xl text-xs font-bold transition-all hover:scale-105 active:scale-95 shadow-sm cursor-pointer">
|
||||||
<svg x-show="!copiedSvg" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg x-show="!copiedSvg" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
||||||
@@ -136,7 +136,8 @@
|
|||||||
|
|
||||||
<!-- Кнопка Скачать SVG -->
|
<!-- Кнопка Скачать SVG -->
|
||||||
<a href="{% url 'hypn0_site:gallery_download' item.s_hash_id %}"
|
<a href="{% url 'hypn0_site:gallery_download' item.s_hash_id %}"
|
||||||
download="hypn0-{{ item.s_hash_id }}.svg"
|
download="hypn0-{{ item.s_hash_id }}.svg"{% if ALLOW_TRACKING %}
|
||||||
|
{# Отслеживание Яндекс #}@click="window.ym && ym(112812760, 'reachGoal', 'download_svg')"{% endif %}
|
||||||
class="flex items-center gap-2 px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl text-xs font-bold transition-all hover:scale-105 active:scale-95 shadow-md border-0 cursor-pointer">
|
class="flex items-center gap-2 px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl text-xs font-bold transition-all hover:scale-105 active:scale-95 shadow-md border-0 cursor-pointer">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
||||||
|
|||||||
+39
-33
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
{% block CONTENT %}
|
{% block CONTENT %}
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-[400px_1fr] gap-8 items-start"
|
<div class="flex flex-col lg:grid lg:grid-cols-[400px_1fr] gap-6 lg:gap-8 items-start"
|
||||||
x-data="{
|
x-data="{
|
||||||
/* Активная вкладка: 'style' (фигуры), 'params' (ползунки), 'color' (палитра) */
|
/* Активная вкладка: 'style' (фигуры), 'params' (ползунки), 'color' (палитра) */
|
||||||
activeTab: 'style',
|
activeTab: 'style',
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
/* Служебные состояния интерфейса */
|
/* Служебные состояния интерфейса */
|
||||||
isLoading: false, /* Идет ли процесс генерации SVG (индикация, блокировка) */
|
isLoading: false, /* Идет ли процесс генерации SVG (индикация, блокировка) */
|
||||||
hasChanges: true, /* Флаг наличия правок в форме для активности кнопки */
|
hasChanges: true, /* Флаг наличия правок в форме для активности кнопки */
|
||||||
|
hasGenerated: false, /* Флаг завершения хотя бы одной генерации */
|
||||||
hasImage: false, /* Флаг загрузки пользовательского файла */
|
hasImage: false, /* Флаг загрузки пользовательского файла */
|
||||||
fileName: '', /* Имя выбранного файла */
|
fileName: '', /* Имя выбранного файла */
|
||||||
imagePreviewUrl: null, /* Превью загруженного файла (Blob URL) */
|
imagePreviewUrl: null, /* Превью загруженного файла (Blob URL) */
|
||||||
@@ -106,7 +107,7 @@
|
|||||||
}">
|
}">
|
||||||
|
|
||||||
<!-- ЛЕВАЯ ПАНЕЛЬ: НАСТРОЙКИ -->
|
<!-- ЛЕВАЯ ПАНЕЛЬ: НАСТРОЙКИ -->
|
||||||
<aside class="space-y-6">
|
<aside class="contents lg:block lg:space-y-6">
|
||||||
<form hx-post="/generate"
|
<form hx-post="/generate"
|
||||||
hx-target="#preview"
|
hx-target="#preview"
|
||||||
hx-trigger="submit"
|
hx-trigger="submit"
|
||||||
@@ -115,8 +116,8 @@
|
|||||||
@change="hasChanges = true"
|
@change="hasChanges = true"
|
||||||
@input="hasChanges = true"
|
@input="hasChanges = true"
|
||||||
@htmx:before-request="startThinking()"
|
@htmx:before-request="startThinking()"
|
||||||
@htmx:after-request="isLoading = false; hasChanges = false"
|
@htmx:after-request="isLoading = false; hasChanges = false; hasGenerated = true"
|
||||||
class="relative bg-white dark:bg-zinc-900 p-6 rounded-2xl shadow-sm border border-stone-200 dark:border-zinc-800 overflow-hidden">
|
class="contents lg:block lg:relative lg:bg-white lg:dark:bg-zinc-900 lg:p-6 lg:rounded-2xl lg:shadow-sm lg:border lg:border-stone-200 lg:dark:border-zinc-800 lg:overflow-hidden">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<!-- Фоновая спираль размышления при генерации (прижата снизу на всю левую панель) -->
|
<!-- Фоновая спираль размышления при генерации (прижата снизу на всю левую панель) -->
|
||||||
@@ -127,37 +128,37 @@
|
|||||||
x-transition:leave="transition ease-in duration-200"
|
x-transition:leave="transition ease-in duration-200"
|
||||||
x-transition:leave-start="opacity-100 scale-100"
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
x-transition:leave-end="opacity-0 scale-95"
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
class="absolute inset-0 z-20 pointer-events-none flex items-end justify-center overflow-hidden rounded-2xl bg-white/70 dark:bg-zinc-900/75 backdrop-blur-[2px]">
|
class="hidden lg:flex absolute inset-0 z-20 pointer-events-none items-end justify-center overflow-hidden rounded-2xl bg-white/70 dark:bg-zinc-900/75 backdrop-blur-[2px]">
|
||||||
<img src="{% static 'img/thinking.svg' %}" alt="thinking"
|
<img src="{% static 'img/thinking.svg' %}" alt="thinking"
|
||||||
class="w-120 h-120 -mb-24 opacity-75 animate-spin [animation-duration:10s]">
|
class="w-120 h-120 -mb-24 opacity-75 animate-spin [animation-duration:10s]">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Загрузка файла -->
|
<!-- Загрузка файла -->
|
||||||
<div class="mb-8">
|
<div class="order-1 w-full bg-white dark:bg-zinc-900 p-3 sm:p-4 lg:p-0 rounded-2xl lg:rounded-none border border-stone-200 dark:border-zinc-800 lg:border-0 shadow-sm lg:shadow-none mb-0 lg:mb-8">
|
||||||
<label class="block text-sm font-bold mb-2 uppercase tracking-wider text-stone-500 dark:text-zinc-400">Источник</label>
|
<label class="hidden lg:block text-sm font-bold mb-2 uppercase tracking-wider text-stone-500 dark:text-zinc-400">Источник</label>
|
||||||
<div class="relative group">
|
<div class="relative group">
|
||||||
<input type="file" name="image" accept="image/*"
|
<input type="file" name="image" accept="image/*"
|
||||||
@change="handleFileChange($event)"
|
@change="handleFileChange($event)"
|
||||||
class="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10">
|
class="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10">
|
||||||
<div
|
<div
|
||||||
:class="hasImage ? 'border-emerald-500 bg-emerald-50/20 dark:bg-emerald-950/20' : 'border-stone-300 dark:border-zinc-700'"
|
:class="hasImage ? 'border-emerald-500 bg-emerald-50/20 dark:bg-emerald-950/20' : 'border-stone-300 dark:border-zinc-700'"
|
||||||
class="border-2 border-dashed group-hover:border-emerald-500 rounded-xl p-6 flex flex-col items-center justify-center text-center transition-all">
|
class="border-2 border-dashed group-hover:border-emerald-500 rounded-xl p-3 sm:p-4 lg:p-6 flex flex-col items-center justify-center text-center transition-all">
|
||||||
|
|
||||||
<!-- Состояние 1: Файл не выбран -->
|
<!-- Состояние 1: Файл не выбран -->
|
||||||
<div x-show="!hasImage" class="text-stone-400 group-hover:text-emerald-500 flex flex-col items-center gap-3 transition-colors">
|
<div x-show="!hasImage" class="text-stone-400 group-hover:text-emerald-500 flex flex-row lg:flex-col items-center gap-3 transition-colors">
|
||||||
<svg class="w-20 h-20 fill-current transition-colors opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
<svg class="w-8 h-8 sm:w-10 sm:h-10 lg:w-20 lg:h-20 shrink-0 fill-current transition-colors opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
<path d="M477 174c8.812-13.708 13.948-29.993 13.948-47.466 0-48.55-39.499-88.048-88.05-88.048-32.839 0-61.516 18.084-76.651 44.803-22.773-5.031-46.356-7.586-70.424-7.586-24.003 0-47.624 2.55-70.427 7.582-15.135-26.717-43.813-44.799-76.65-44.799-48.55 0-88.048 39.498-88.048 88.049 0 17.473 5.137 33.759 13.949 47.467C11.987 204.676 0 239.016 0 274.61c0 53.849 27.194 104.194 76.573 141.763C124.677 452.97 188.399 473.125 256 473.125s131.323-20.155 179.427-56.753C484.806 378.803 512 328.457 512 274.609c0-35.59-11.986-69.935-34.822-100.219zM403.076 70.224c31.266 0 56.703 25.436 56.703 56.701s-25.437 56.702-56.703 56.702-56.701-25.436-56.701-56.702 25.436-56.701 56.701-56.701zm-294.152 0c31.266 0 56.703 25.436 56.703 56.701s-25.437 56.702-56.703 56.702-56.701-25.436-56.701-56.702 25.435-56.701 56.701-56.701zM56.629 197.71a87.57 87.57 0 0 0 52.294 17.264c48.55 0 88.05-39.498 88.05-88.049a88.25 88.25 0 0 0-1.02-13.409c19.464-4.03 39.588-6.075 60.047-6.075 20.515 0 40.613 2.041 60.048 6.068-.672 4.376-1.021 8.857-1.021 13.417 0 48.55 39.498 88.049 88.049 88.049 19.568 0 37.66-6.424 52.294-17.263 15.157 21.633 23.712 45.416 25.08 69.911-23.821 20.076-49.402 34.122-79.949 43.943-38.207 12.281-85.474 18.252-144.5 18.252s-106.292-5.971-144.499-18.253c-30.549-9.821-56.132-23.869-79.952-43.945 1.369-24.496 9.921-48.278 25.079-69.91zm199.372 244.067c-107.337 0-197.317-56.31-219.433-131.312 19.882 13.102 41.246 23.196 65.34 30.94 41.355 13.295 91.76 19.758 154.094 19.758s112.738-6.463 154.095-19.758c24.092-7.745 45.456-17.839 65.338-30.939-22.12 75.002-112.099 131.311-219.434 131.311z"/><use href="#B"/><use href="#B" x="294.153"/><path d="M219.852 256.624c-9.79 0-17.756 8.048-17.756 17.986s7.966 17.992 17.756 17.992c9.802 0 17.767-8.055 17.767-17.992s-7.965-17.986-17.767-17.986zm72.163-.14c-9.885 0-17.902 8.115-17.902 18.126s8.017 18.126 17.902 18.126c9.872 0 17.89-8.115 17.89-18.126s-8.018-18.126-17.89-18.126z"/><defs><path id="B" d="M108.931 105.926c-11.45 0-20.738 9.401-20.738 20.998s9.288 20.998 20.738 20.998c11.436 0 20.725-9.401 20.725-20.998s-9.289-20.998-20.725-20.998z"/></defs></svg>
|
<path d="M477 174c8.812-13.708 13.948-29.993 13.948-47.466 0-48.55-39.499-88.048-88.05-88.048-32.839 0-61.516 18.084-76.651 44.803-22.773-5.031-46.356-7.586-70.424-7.586-24.003 0-47.624 2.55-70.427 7.582-15.135-26.717-43.813-44.799-76.65-44.799-48.55 0-88.048 39.498-88.048 88.049 0 17.473 5.137 33.759 13.949 47.467C11.987 204.676 0 239.016 0 274.61c0 53.849 27.194 104.194 76.573 141.763C124.677 452.97 188.399 473.125 256 473.125s131.323-20.155 179.427-56.753C484.806 378.803 512 328.457 512 274.609c0-35.59-11.986-69.935-34.822-100.219zM403.076 70.224c31.266 0 56.703 25.436 56.703 56.701s-25.437 56.702-56.703 56.702-56.701-25.436-56.701-56.702 25.436-56.701 56.701-56.701zm-294.152 0c31.266 0 56.703 25.436 56.703 56.701s-25.437 56.702-56.703 56.702-56.701-25.436-56.701-56.702 25.435-56.701 56.701-56.701zM56.629 197.71a87.57 87.57 0 0 0 52.294 17.264c48.55 0 88.05-39.498 88.05-88.049a88.25 88.25 0 0 0-1.02-13.409c19.464-4.03 39.588-6.075 60.047-6.075 20.515 0 40.613 2.041 60.048 6.068-.672 4.376-1.021 8.857-1.021 13.417 0 48.55 39.498 88.049 88.049 88.049 19.568 0 37.66-6.424 52.294-17.263 15.157 21.633 23.712 45.416 25.08 69.911-23.821 20.076-49.402 34.122-79.949 43.943-38.207 12.281-85.474 18.252-144.5 18.252s-106.292-5.971-144.499-18.253c-30.549-9.821-56.132-23.869-79.952-43.945 1.369-24.496 9.921-48.278 25.079-69.91zm199.372 244.067c-107.337 0-197.317-56.31-219.433-131.312 19.882 13.102 41.246 23.196 65.34 30.94 41.355 13.295 91.76 19.758 154.094 19.758s112.738-6.463 154.095-19.758c24.092-7.745 45.456-17.839 65.338-30.939-22.12 75.002-112.099 131.311-219.434 131.311z"/><use href="#B"/><use href="#B" x="294.153"/><path d="M219.852 256.624c-9.79 0-17.756 8.048-17.756 17.986s7.966 17.992 17.756 17.992c9.802 0 17.767-8.055 17.767-17.992s-7.965-17.986-17.767-17.986zm72.163-.14c-9.885 0-17.902 8.115-17.902 18.126s8.017 18.126 17.902 18.126c9.872 0 17.89-8.115 17.89-18.126s-8.018-18.126-17.89-18.126z"/><defs><path id="B" d="M108.931 105.926c-11.45 0-20.738 9.401-20.738 20.998s9.288 20.998 20.738 20.998c11.436 0 20.725-9.401 20.725-20.998s-9.289-20.998-20.725-20.998z"/></defs></svg>
|
||||||
<span>Покорми Гипнoжабу,<br />притащи сюда img или кликни</span>
|
<span class="text-xs sm:text-sm lg:text-base font-medium text-left lg:text-center">Покорми Гипнoжабу,<br class="hidden lg:inline" /> притащи сюда img или кликни</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Состояние 2: Файл выбран -->
|
<!-- Состояние 2: Файл выбран -->
|
||||||
<div x-show="hasImage" class="flex flex-col items-center gap-3 text-emerald-600 dark:text-emerald-400">
|
<div x-show="hasImage" class="flex flex-row lg:flex-col items-center gap-3 text-emerald-600 dark:text-emerald-400 text-left lg:text-center">
|
||||||
<img :src="imagePreviewUrl" alt="Превью"
|
<img :src="imagePreviewUrl" alt="Превью"
|
||||||
class="w-24 h-24 object-cover rounded-xl border-2 border-emerald-500 shadow-md">
|
class="w-12 h-12 sm:w-14 sm:h-14 lg:w-24 lg:h-24 shrink-0 object-cover rounded-xl border-2 border-emerald-500 shadow-md">
|
||||||
<div class="text-xs font-mono">
|
<div class="text-xs font-mono">
|
||||||
<p class="font-bold">Гипножаба облизывается на:</p>
|
<p class="font-bold">Гипножаба облизывается на:</p>
|
||||||
<p class="truncate max-w-[260px] text-stone-700 dark:text-zinc-300 font-semibold mt-0.5" x-text="fileName"></p>
|
<p class="truncate max-w-[200px] sm:max-w-[260px] text-stone-700 dark:text-zinc-300 font-semibold mt-0.5" x-text="fileName"></p>
|
||||||
<p class="text-[11px] text-stone-400 dark:text-zinc-500 mt-1">кликни или притащи другой файл, чтобы скормить</p>
|
<p class="text-[10px] sm:text-[11px] text-stone-400 dark:text-zinc-500 mt-0.5">кликни или притащи другой файл</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -165,6 +166,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Блок настроек: табы и параметры -->
|
||||||
|
<div class="order-5 w-full bg-white dark:bg-zinc-900 p-4 sm:p-6 lg:p-0 rounded-2xl lg:rounded-none border border-stone-200 dark:border-zinc-800 lg:border-0 shadow-sm lg:shadow-none">
|
||||||
<!-- Табы настроек -->
|
<!-- Табы настроек -->
|
||||||
<nav class="flex border-b border-stone-200 dark:border-zinc-800 mb-6 overflow-x-auto no-scrollbar">
|
<nav class="flex border-b border-stone-200 dark:border-zinc-800 mb-6 overflow-x-auto no-scrollbar">
|
||||||
<button type="button" @click="activeTab = 'style'"
|
<button type="button" @click="activeTab = 'style'"
|
||||||
@@ -419,9 +422,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Индикатор размышления / статус активности -->
|
<!-- Индикатор размышления / статус активности (только десктоп) -->
|
||||||
<div class="h-8 mt-2 flex items-center justify-center">
|
<div class="hidden lg:flex h-8 mt-2 items-center justify-center">
|
||||||
<div x-show="isLoading"
|
<div x-show="isLoading"
|
||||||
x-transition
|
x-transition
|
||||||
class="flex items-center gap-2 px-3.5 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/30 text-emerald-600 dark:text-emerald-400 text-xs font-bold font-mono tracking-wide animate-pulse">
|
class="flex items-center gap-2 px-3.5 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/30 text-emerald-600 dark:text-emerald-400 text-xs font-bold font-mono tracking-wide animate-pulse">
|
||||||
@@ -440,11 +444,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
:disabled="isLoading || !hasChanges"
|
:disabled="isLoading || !hasChanges"{% if ALLOW_TRACKING %}
|
||||||
|
{# СТАРТ ГЛЮКОТРОН: Отслеживание Яндекс #}@click="window.ym && ym(112812760, 'reachGoal', 'start_gluco')"{% endif %}
|
||||||
:class="(!hasChanges || isLoading)
|
:class="(!hasChanges || isLoading)
|
||||||
? 'bg-stone-200 dark:bg-zinc-800 text-stone-400 dark:text-zinc-600 cursor-not-allowed shadow-none'
|
? 'bg-stone-200 dark:bg-zinc-800 text-stone-400 dark:text-zinc-600 cursor-not-allowed shadow-none'
|
||||||
: 'bg-emerald-600 hover:bg-emerald-500 text-white shadow-lg shadow-emerald-900/20 active:scale-95 cursor-pointer'"
|
: 'bg-emerald-600 hover:bg-emerald-500 text-white shadow-lg shadow-emerald-900/20 active:scale-95 cursor-pointer'"
|
||||||
class="w-full mt-2 font-bold py-3.5 px-6 rounded-xl transition-all flex items-center justify-center gap-2">
|
class="order-3 w-full mt-0 lg:mt-2 font-bold py-3.5 px-6 rounded-xl transition-all flex items-center justify-center gap-2">
|
||||||
<template x-if="isLoading">
|
<template x-if="isLoading">
|
||||||
<span class="flex items-center gap-2">
|
<span class="flex items-center gap-2">
|
||||||
<img src="{% static 'img/thinking.svg' %}" class="w-5 h-5 animate-spin [animation-duration:2s]">
|
<img src="{% static 'img/thinking.svg' %}" class="w-5 h-5 animate-spin [animation-duration:2s]">
|
||||||
@@ -459,35 +464,36 @@
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- ПРАВАЯ ПАНЕЛЬ: ПРЕВЬЮ -->
|
<!-- ПРАВАЯ ПАНЕЛЬ: ПРЕВЬЮ -->
|
||||||
<section class="flex flex-col gap-6">
|
<section class="contents lg:flex lg:flex-col lg:gap-6">
|
||||||
<div id="preview"
|
<div id="preview"
|
||||||
class="w-full h-[744px] bg-stone-200 dark:bg-zinc-900 rounded-3xl flex items-center justify-center border border-stone-300 dark:border-zinc-800 overflow-hidden relative group">
|
:class="hasGenerated ? 'h-auto min-h-[280px] aspect-square lg:aspect-auto lg:h-[744px]' : 'h-14 sm:h-16 lg:h-[744px]'"
|
||||||
|
class="order-2 w-full bg-stone-200 dark:bg-zinc-900 rounded-2xl lg:rounded-3xl flex items-center justify-center border border-stone-300 dark:border-zinc-800 overflow-hidden relative group transition-all duration-300">
|
||||||
|
|
||||||
<div class="text-stone-400 flex flex-col items-center gap-4 text-center px-6">
|
<div class="text-stone-400 flex flex-row lg:flex-col items-center gap-2.5 lg:gap-4 text-center px-4 lg:px-6 py-2 lg:py-0">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-16 h-16 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 lg:w-16 lg:h-16 opacity-50 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||||||
:class="isLoading ? 'animate-pulse text-emerald-500 opacity-80' : 'opacity-50'">
|
:class="isLoading ? 'animate-pulse text-emerald-500 opacity-80' : 'opacity-50'">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<p class="transition-all duration-300"
|
<p class="transition-all duration-300 text-xs sm:text-sm lg:text-base font-mono"
|
||||||
:class="isLoading ? 'text-emerald-600 dark:text-emerald-400 font-mono text-xs font-bold uppercase tracking-wider animate-pulse' : 'font-medium opacity-50'"
|
:class="isLoading ? 'text-emerald-600 dark:text-emerald-400 font-mono text-xs font-bold uppercase tracking-wider animate-pulse' : 'font-medium opacity-60'"
|
||||||
x-text="isLoading ? 'Гипножаба переваривает пиксели…' : 'Ваше искусство появится здесь'"></p>
|
x-text="isLoading ? 'Гипножаба переваривает пиксели…' : 'Ваше искусство появится здесь'"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{# КНОПКИ КОПИРОВАНИЕ И СКАЧИВАНИЕ (ПОД ПРЕВЬЮ)#}
|
{# КНОПКИ КОПИРОВАНИЕ И СКАЧИВАНИЕ (ПОД ПРЕВЬЮ)#}
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="order-4 grid grid-cols-2 gap-4 w-full">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="copySvg()"
|
@click="copySvg(){% if ALLOW_TRACKING %}; window.ym && ym(112812760, 'reachGoal', 'copy_svg'){% endif %}"
|
||||||
class="bg-stone-800 text-white dark:bg-zinc-800 py-3.5 rounded-xl font-bold flex items-center justify-center gap-2 hover:bg-stone-700 transition-colors cursor-pointer active:scale-95">
|
class="bg-stone-800 text-white dark:bg-zinc-800 py-3.5 rounded-xl font-bold flex items-center justify-center gap-2 hover:bg-stone-700 transition-colors cursor-pointer active:scale-95 text-xs sm:text-sm md:text-base">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>
|
<path d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>
|
||||||
<span x-text="copied ? 'СКОПИРОВАНО В БУФЕР!' : 'КОПИРОВАТЬ SVG'"></span>
|
<span x-text="copied ? 'СКОПИРОВАНО В БУФЕР!' : 'КОПИРОВАТЬ SVG'"></span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="downloadSvg()"
|
@click="downloadSvg(){% if ALLOW_TRACKING %}; window.ym && ym(112812760, 'reachGoal', 'download_svg'){% endif %}"
|
||||||
class="bg-emerald-600 text-white py-3.5 rounded-xl font-bold flex items-center justify-center gap-2 hover:bg-emerald-500 transition-colors shadow-lg shadow-emerald-900/20 cursor-pointer active:scale-95">
|
class="bg-emerald-600 text-white py-3.5 rounded-xl font-bold flex items-center justify-center gap-2 hover:bg-emerald-500 transition-colors shadow-lg shadow-emerald-900/20 cursor-pointer active:scale-95 text-xs sm:text-sm md:text-base">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
|
<svg class="w-5 h-5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
|
||||||
СКАЧАТЬ
|
СКАЧАТЬ
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
@@ -1,98 +1,51 @@
|
|||||||
// analytics.js — Аналитика и счетчики посещений для hypn0.xyz
|
// analytics.js — Аналитика и счетчики посещений для hypn0.xyz
|
||||||
// Версия: 1.0 | Дата: 2026-05-15
|
// Версия: 1.2 | Дата: 2026-09-16
|
||||||
// Содержит: Google Analytics 4, Yandex.Metrika, Top.Mail.Ru
|
// Содержит: Google Tag Manager, Yandex.Metrika
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Google Analytics 4 (GA4)
|
// Google Tag Manager (GTM)
|
||||||
// ID: GT-XXXXXXXX
|
// ID: GTM-5FVH3KMJ
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
(function() {
|
(function(w, d, s, l, i) {
|
||||||
var script = document.createElement('script');
|
w[l] = w[l] || [];
|
||||||
script.async = true;
|
w[l].push({
|
||||||
script.src = 'https://www.googletagmanager.com/gtag/js?id=GT-XXXXXXXX';
|
'gtm.start': new Date().getTime(),
|
||||||
document.head.appendChild(script);
|
event: 'gtm.js'
|
||||||
|
});
|
||||||
window.dataLayer = window.dataLayer || [];
|
var f = d.getElementsByTagName(s)[0],
|
||||||
function gtag(){dataLayer.push(arguments);}
|
j = d.createElement(s),
|
||||||
window.gtag = gtag;
|
dl = l != 'dataLayer' ? '&l=' + l : '';
|
||||||
gtag('js', new Date());
|
j.async = true;
|
||||||
gtag('config', 'GT-XXXXXXXX');
|
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
|
||||||
})();
|
f.parentNode.insertBefore(j, f);
|
||||||
|
})(window, document, 'script', 'dataLayer', 'GTM-5FVH3KMJ');
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Yandex.Metrika (Яндекс.Метрика)
|
// Yandex.Metrika (Яндекс.Метрика)
|
||||||
// ID: 12345678
|
// ID: 112812760
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
(function() {
|
(function(m,e,t,r,i,k,a){
|
||||||
window.ym = window.ym || function(){
|
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||||
(window.ym.a = window.ym.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; }}
|
||||||
window.ym.l = 1 * new Date();
|
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?id=112812760', 'ym');
|
||||||
|
|
||||||
// Загружаем скрипт Метрики
|
window.ym(112812760, 'init', {
|
||||||
var script = document.createElement('script');
|
ssr: true,
|
||||||
script.type = 'text/javascript';
|
webvisor: true,
|
||||||
script.src = 'https://mc.yandex.ru/metrika/tag.js';
|
|
||||||
document.head.appendChild(script);
|
|
||||||
|
|
||||||
// Инициализируем Метрику
|
|
||||||
window.ym(12345678, 'init', {
|
|
||||||
trackHash: true,
|
|
||||||
clickmap: true,
|
clickmap: true,
|
||||||
|
ecommerce: 'dataLayer',
|
||||||
referrer: document.referrer,
|
referrer: document.referrer,
|
||||||
url: location.href,
|
url: location.href,
|
||||||
accurateTrackBounce: true,
|
accurateTrackBounce: true,
|
||||||
trackLinks: true
|
trackLinks: true
|
||||||
});
|
});
|
||||||
})();
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Top.Mail.Ru counter (Рейтинг@Mail.ru)
|
|
||||||
// ID: 1234567
|
|
||||||
// ============================================================================
|
|
||||||
(function() {
|
|
||||||
var _tmr = window._tmr || (window._tmr = []);
|
|
||||||
_tmr.push({
|
|
||||||
id: "1234567",
|
|
||||||
type: "pageView",
|
|
||||||
start: (new Date()).getTime()
|
|
||||||
});
|
|
||||||
|
|
||||||
(function(d, w, id) {
|
|
||||||
if (d.getElementById(id)) return;
|
|
||||||
var ts = d.createElement("script");
|
|
||||||
ts.type = "text/javascript";
|
|
||||||
ts.async = true;
|
|
||||||
ts.id = id;
|
|
||||||
ts.src = "https://top-fwz1.mail.ru/js/code.js";
|
|
||||||
|
|
||||||
var f = function() {
|
|
||||||
var s = d.getElementsByTagName("script")[0];
|
|
||||||
s.parentNode.insertBefore(ts, s);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (w.opera == "[object Opera]") {
|
|
||||||
d.addEventListener("DOMContentLoaded", f, false);
|
|
||||||
} else {
|
|
||||||
f();
|
|
||||||
}
|
|
||||||
})(document, window, "tmr-code");
|
|
||||||
|
|
||||||
// Добавляем изображение для noscript
|
|
||||||
if (!window.noScriptAdded) {
|
|
||||||
window.noScriptAdded = true;
|
|
||||||
var noscriptDiv = document.createElement('div');
|
|
||||||
noscriptDiv.style.display = 'none';
|
|
||||||
noscriptDiv.innerHTML = '<img src="https://top-fwz1.mail.ru/counter?id=1234567;js=na" style="position:absolute;left:-9999px;" alt="Top.Mail.Ru" />';
|
|
||||||
document.body.appendChild(noscriptDiv);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Экспортируем gtag в глобальный контекст для возможности использования в коде
|
// window.dataLayer доступна через глобальную переменную
|
||||||
// window.gtag доступна через глобальную переменную
|
|
||||||
|
|
||||||
|
|||||||
+20
-48
@@ -1,9 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Сборка Tailwind CSS v3 для фронтенда HYPN0
|
# Сборка Tailwind CSS v4 для фронтенда HYPN0
|
||||||
# Запуск из корня проекта: bash ./scripts/build-tailwind.sh
|
# Запуск из корня проекта: bash ./scripts/build-tailwind.sh
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
||||||
|
|
||||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
TAILWIND_DIR="$PROJECT_ROOT/frontend-assembly/tailwind"
|
TAILWIND_DIR="$PROJECT_ROOT/frontend-assembly/tailwind"
|
||||||
OUTPUT_DIR="$PROJECT_ROOT/public/static/css"
|
OUTPUT_DIR="$PROJECT_ROOT/public/static/css"
|
||||||
@@ -18,8 +20,6 @@ fail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# cleanup() — удаляет временные файлы при любом завершении скрипта.
|
# cleanup() — удаляет временные файлы при любом завершении скрипта.
|
||||||
# Все рабочие файлы (postcss.config.js, tailwind.config.js, input.css)
|
|
||||||
# создаются здесь и удаляются в EXIT/INT/TERM.
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
rm -rf "$TAILWIND_DIR/node_modules" \
|
rm -rf "$TAILWIND_DIR/node_modules" \
|
||||||
"$TAILWIND_DIR/postcss.config.js" \
|
"$TAILWIND_DIR/postcss.config.js" \
|
||||||
@@ -37,67 +37,39 @@ if [[ ! -f "$TAILWIND_DIR/package.json" ]]; then
|
|||||||
fail "Не найден package.json: $TAILWIND_DIR/package.json"
|
fail "Не найден package.json: $TAILWIND_DIR/package.json"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f "$TAILWIND_DIR/package-lock.json" ]]; then
|
|
||||||
fail "Не найден package-lock.json: $TAILWIND_DIR/package-lock.json"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$OUTPUT_DIR"
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
# --- tailwind.config.js ---
|
|
||||||
log "Создаю tailwind.config.js"
|
|
||||||
cat > "$TAILWIND_DIR/tailwind.config.js" <<'TWEOF'
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
content: [
|
|
||||||
'../../hypn0/templates/**/*.html',
|
|
||||||
'../../hypn0/hypn0_site/**/*.py',
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
}
|
|
||||||
TWEOF
|
|
||||||
|
|
||||||
# --- postcss.config.js ---
|
|
||||||
log "Создаю postcss.config.js"
|
|
||||||
cat > "$TAILWIND_DIR/postcss.config.js" <<'PCEOF'
|
|
||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
PCEOF
|
|
||||||
|
|
||||||
# --- input.css ---
|
# --- input.css ---
|
||||||
log "Создаю input.css"
|
log "Создаю input.css для Tailwind v4"
|
||||||
cat > "$TAILWIND_DIR/input.css" <<'EOF'
|
cat > "$TAILWIND_DIR/input.css" <<'EOF'
|
||||||
/*
|
/*
|
||||||
input.css — точка входа для сборки prod-версии Tailwind CSS v3.
|
input.css — точка входа для сборки prod-версии Tailwind CSS v4.
|
||||||
|
|
||||||
Собирается через `npm run build` (PostCSS) в
|
Собирается через @tailwindcss/cli в
|
||||||
файл public/static/css/tailwind.min.css, который подключается в _base.html
|
файл public/static/css/tailwind.min.css, который подключается в _base.html
|
||||||
для production (когда settings.DEBUG == False).
|
для production (когда settings.DEBUG == False).
|
||||||
|
|
||||||
Здесь же подключаются кастомные стили из hypn0/templates/css/tailwind-custom.css —
|
Здесь же подключаются кастомные стили из hypn0/templates/css/tailwind-custom.css.
|
||||||
того же самого файла, который в dev-режиме подключается через {% include %} внутрь
|
|
||||||
инлайнового <style type="text/tailwindcss"> в _base.html.
|
|
||||||
|
|
||||||
Это обеспечивает единый источник кастомных стилей для dev и prod.
|
|
||||||
*/
|
*/
|
||||||
@tailwind base;
|
@import "tailwindcss";
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
@source "../../hypn0/templates";
|
||||||
|
@source "../../hypn0/hypn0_site";
|
||||||
|
|
||||||
@import "../../hypn0/templates/css/tailwind-custom.css";
|
@import "../../hypn0/templates/css/tailwind-custom.css";
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
log "СОБИРАЮ Tailwind CSS v3"
|
log "СОБИРАЮ Tailwind CSS v4"
|
||||||
cd "$TAILWIND_DIR"
|
cd "$TAILWIND_DIR"
|
||||||
|
|
||||||
log 'Устанавливаю зависимости через npm ci'
|
# Если package-lock.json отсутствует или устарел, используем npm install / npm ci
|
||||||
npm ci
|
if [[ -f "$TAILWIND_DIR/package-lock.json" ]]; then
|
||||||
|
log 'Устанавливаю зависимости через npm install'
|
||||||
|
npm install --no-audit --no-fund
|
||||||
|
else
|
||||||
|
log 'Устанавливаю зависимости через npm install'
|
||||||
|
npm install --no-audit --no-fund
|
||||||
|
fi
|
||||||
|
|
||||||
log 'Собираю CSS'
|
log 'Собираю CSS'
|
||||||
npm run build
|
npm run build
|
||||||
|
|||||||
Reference in New Issue
Block a user