41 Commits

Author SHA1 Message Date
e55ea68d05 ---: minor 2026-03-25 14:32:14 +03:00
a537bc9c37 mod: вернул борку для ARM64. Добавил очистку кеша poetry
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 3m18s
2026-03-21 16:51:16 +03:00
a62e02ddcb ---: убрал сборку для ARM64
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m5s
2026-03-21 16:47:16 +03:00
d294e5a951 fix: правильный map для тома с media
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 1m21s
2026-03-21 16:37:18 +03:00
fd5553e326 mod: обновление пакетов 2026-03-21 16:21:39 +03:00
5b5e8dd096 ---: minor (комментарии)
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 39s
2026-03-21 15:53:27 +03:00
906888ae73 mod: формирование правильного canonical для index-страницы 2026-03-21 15:52:43 +03:00
ac9109ea6e mod: Поле вкл/выкл типограф перенесено во вкладку настроек типографа 2026-03-21 15:51:49 +03:00
9027f12792 mod: ленивое формирование тегов (чтобы в проде уменьшить нагрузку на gunicorn) 2026-03-21 15:17:12 +03:00
4ddd787f5d ---: minor (comments) 2026-03-21 14:42:05 +03:00
a60972d70b fix: volumes for database -- /home/app/web/database 2026-03-20 19:10:24 +03:00
8fa96cae66 fix: del SQLite files 2026-03-20 19:09:27 +03:00
7f96932a40 fix: directory with proper permissions 1000:1000 in Dockerfile
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 45s
2026-03-20 17:31:24 +03:00
d59c867010 fix: increase SQLite timeout to 60s and enable WAL mode for better concurrency with multiple Gunicorn workers
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 36s
2026-03-20 03:05:38 +03:00
9ea851ad95 fix: add /app/database directory creation with app:app ownership in Dockerfile
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 34s
2026-03-20 02:28:58 +03:00
5146f88c7d mod: fix: add sqlite3 utility to final stage Dockerfile for manage.py dbshell support
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m3s
2026-03-20 01:52:03 +03:00
20ecb9cc4c fix: set X-Forwarded-Proto to https explicitly for internal proxying to Docker 2026-03-20 01:09:08 +03:00
ea2d352cae fix: copy /usr/local/bin from builder stage to include gunicorn executable
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 36s
2026-03-20 00:51:53 +03:00
d459eedf41 fix: create media/errors directory with proper permissions in Dockerfile
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 33s
2026-03-20 00:43:42 +03:00
9d31429e14 fix: uncomment chown for nginx_configs_host and correct volume path to ./config
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 33s
2026-03-20 00:29:54 +03:00
c5963d1d30 mod: Dockerfile .
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 33s
2026-03-20 00:19:39 +03:00
12001bc749 mod: Dockerfile
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 22s
2026-03-20 00:17:31 +03:00
99a2ace43f fix: arm64 bild & create nginx config directory with proper permissions before USER app
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 2m36s
2026-03-19 23:57:07 +03:00
81efaf1ba5 fix: correct volume path in docker-compose.prod.yml and add model migration
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 28s
2026-03-19 23:34:55 +03:00
42b378fcbc fix: correct sed command and paths in docker-compose.prod.yml for proper nginx config generation 2026-03-19 19:33:12 +03:00
ba4175dfdb mod: больше времени на сборку
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 2m20s
2026-03-19 17:24:35 +03:00
4194b351d2 mod: собираем образы только linux/amd64 2026-03-19 17:22:31 +03:00
be68a82927 fix: dockerfile - add AS keywords and create staticfiles directory (root)
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 3m5s
2026-03-19 17:12:11 +03:00
53b127a966 fix: dockerfile - add AS keywords and create staticfiles directory
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 1m57s
2026-03-19 17:06:05 +03:00
746c50a988 fix: rebuild with new docker configuration and healthcheck support
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 2m5s
2026-03-19 17:00:00 +03:00
2520362ad5 mod: Новая версия типографа etpgrf и стили для висячей пунктуации
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 2m27s
2026-03-19 15:37:38 +03:00
a857101c3f mod: улучшения для видимости ИИ 2026-03-19 13:39:44 +03:00
7eeb44a1f5 fix: canonical в продакшен не должен показывать http вместо https
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 2m0s
2026-03-05 14:02:59 +03:00
86bfd9b07b fix: "обход" скрытых миграций TagIt для продакшн.
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m34s
2026-02-25 21:31:33 +03:00
c3c81d7ff5 add: Добавлен select2 для управления тегами
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 1m20s
2026-02-25 21:10:11 +03:00
f4cce3d08a mod: Корректная проверка обновлений каждый 30 минут (1800 сек.) 2026-02-23 20:03:06 +03:00
45275c51f6 add: Счетчик Google Analytics (GA4 - поток Goofle Tag) 2026-02-23 19:58:29 +03:00
f2f98d9229 add: Счетчик метрики
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m26s
2026-02-22 13:01:16 +03:00
a33b517a3e add: Планы (памятка) 2026-02-22 13:00:49 +03:00
d4624e7761 mod: Улучшения для мобильных устройств. 2026-02-22 12:12:49 +03:00
a608dea61f mod: Страницы ошибок (в новом дизайне и оптимизированы). 2026-02-22 02:55:21 +03:00
39 changed files with 1041 additions and 544 deletions

View File

@@ -61,5 +61,11 @@ jobs:
# Собираем под текущую архитектуру (linux/amd64). # Собираем под текущую архитектуру (linux/amd64).
# Если сервер и MacMini на разных архитектурах (x86 vs ARM), добавьте нужные, например: linux/amd64,linux/arm64 # Если сервер и MacMini на разных архитектурах (x86 vs ARM), добавьте нужные, например: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
# platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
# ДОБАВЛЕНО:
cache-from: type=gha
cache-to: type=gha,mode=max
# И это для медленного интернета:
timeout: 900 # 15 минут на всю сборку

11
.gitignore vendored
View File

@@ -262,8 +262,17 @@ cython_debug/
public/media/* public/media/*
!public/media/README.md !public/media/README.md
# OS specific # OS specific (Synology DSM)
.DS_Store .DS_Store
# Data Backup # Data Backup
database/data.json database/data.json
# База данных SQLite
# Никогда не копировать локальную базу в образ!
db.sqlite3
db.sqlite3-journal
db.sqlite3-shm
db.sqlite3-wal
*.sqlite3
*.sqlite3-journal

View File

@@ -1,63 +1,108 @@
# ========================================== # =================================================
# Dockerfile для Django + Gunicorn + WhiteNoise # STAGE 1: Builder - Установка зависимостей
# ========================================== # =================================================
FROM python:3.12-slim AS builder
# 1. Базовый образ: Python 3.12 (Slim версия для меньшего размера) # Устанавливаем переменные окружения
FROM python:3.12-slim
# 2. Переменные окружения для Python
# PYTHONDONTWRITEBYTECODE: Запрещает Python писать .pyc файлы
# PYTHONUNBUFFERED: Гарантирует, что вывод консоли (logs) виден сразу (не буферизуется)
ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
# Poetry настройки: не создавать виртуальное окружение внутри контейнера (ставим системно). # Говорим Poetry, чтобы он не создавал venv, а ставил пакеты в системный site-packages
# Дублирует `poetry config virtualenvs.create false` в пп.7 (на всякий случай).
ENV POETRY_VIRTUALENVS_CREATE=false ENV POETRY_VIRTUALENVS_CREATE=false
# Путь настройки Django (по умолчанию для production) на случай если контейнер будет запущен не через docker-compose.
ENV DJANGO_SETTINGS_MODULE=dicquo.settings
# 3. Рабочая директория внутри контейнера # Устанавливаем системные зависимости, необходимые для СБОРКИ пакетов (например, Pillow)
WORKDIR /app # build-essential нужен для компиляции, -dev пакеты для сборки Pillow
# 4. Установка системных зависимостей
# - libjpeg-dev zlib1g-dev: библиотеки для работы с изображениями (Pillow)
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libjpeg-dev \ libjpeg-dev \
zlib1g-dev \ zlib1g-dev \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 5. Установка Poetry через pip (быстро и надежно) # Устанавливаем Poetry
RUN pip install --no-cache-dir poetry RUN pip install --no-cache-dir poetry
# 6. Копируем файлы зависимостей (pyproject.toml и poetry.lock) # Создаем рабочую директорию
# Делаем это ДО копирования всего кода, чтобы использовать кэш Docker layers. WORKDIR /app
# Копируем только файлы зависимостей для кэширования этого слоя
COPY pyproject.toml poetry.lock /app/ COPY pyproject.toml poetry.lock /app/
# 7. Установка зависимостей проекта # Устанавливаем зависимости проекта. Poetry установит их в /usr/local/lib/python3.12/site-packages
# --no-interaction: не будет спрашивать подтверждения RUN poetry install --no-interaction --no-ansi --no-root --only main
# --no-ansi: уберваем цветные символы из логов сборки (они иногда мусорят)
# --no-root: не устанавливать сам проект как пакет (мы просто копируем код)
# --only main: не ставить dev-зависимости (тесты, линтеры и т.п.) для продакшена
# RUN poetry install --no-root --only main
# Настройка Poetry: не создавать venv и установка зависимостей (без dev-зависимостей для продакшена)
RUN poetry config virtualenvs.create false \
&& poetry install --no-interaction --no-ansi --no-root --only main
# 8. Копируем весь исходный код проекта в контейнер # Очищаем кэш Poetry, чтобы уменьшить размер слоя
COPY . /app/ RUN poetry cache clear --all -n
# 9. Сборка статики (CSS, JS)
# Важно: Запускаем collectstatic с фейковым SECRET_KEY, так как на этапе сборки env файла может не быть.
RUN SECRET_KEY=dummy_build_key python dicquo/manage.py collectstatic --noinput --clear
# 10. Открываем порт 8000 # =================================================
# STAGE 2: Final - Создание чистого и безопасного образа
# =================================================
FROM python:3.12-slim AS stage-final
# Устанавливаем переменные окружения
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV DJANGO_SETTINGS_MODULE=dicquo.settings
# Устанавливаем только RUNTIME системные зависимости.
# Пакеты -dev и build-essential здесь не нужны.
RUN apt-get update && apt-get install -y --no-install-recommends \
libjpeg62-turbo \
sqlite3 \
&& rm -rf /var/lib/apt/lists/*
# Создаем пользователя без прав root для безопасности
# RUN addgroup --system app && adduser --system --ingroup app app
# Создаем рабочую директорию
WORKDIR /home/app/web
# Копируем установленные Python-пакеты из builder-стадии
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
# Копируем исполняемые файлы (gunicorn, pip и т.д.)
COPY --from=builder /usr/local/bin /usr/local/bin
# Копируем исходный код проекта и устанавливаем правильного владельца
# ИЗМЕНЕНИЕ: app:app -> 1000:1000
COPY --chown=1000:1000 . .
# Создаём директорию для конфигов nginx и даём права пользователю app
# Это выполняется ещё от root, поэтому проблем с permissions не будет.
RUN mkdir -p /nginx_configs_host/nginx && chown -R 1000:1000 /nginx_configs_host
# Создаём директорию для собранной статики и даём права пользователю app
RUN mkdir -p /home/app/web/staticfiles && chown -R 1000:1000 /home/app/web/staticfiles
# Создаём директорию для ошибок (404, 500) и даём права пользователю app
RUN mkdir -p /app/public/media/errors && chown -R 1000:1000 /app/public/media
# Создаём директорию для БД и даём права пользователю app
# Это важно когда БД монтируется как том с хоста
RUN mkdir -p /app/database && chown -R 1000:1000 /app/database
# Переключаемся на пользователя без прав root
USER 1000
# Собираем статику
# Используем dummy ключ, так как .env файла нет на этапе сборки
RUN SECRET_KEY=dummy python dicquo/manage.py collectstatic --noinput --clear
# Открываем порт
EXPOSE 8000 EXPOSE 8000
# 11. Команда запуска # Проверка здоровья контейнера
# Переходим в подпапку dicquo, где лежит код Django проекта # Docker будет периодически проверять, жив ли контейнер, отправляя GET запрос к главной странице.
WORKDIR /app/dicquo # Параметры:
# --interval=30s - проверка каждые 30 секунд
# --timeout=3s - ожидаем ответ максимум 3 секунды
# --start-period=10s - даем контейнеру 10 секунд на запуск перед первой проверкой
# --retries=3 - объявляем контейнер unhealthy после 3 неудачных попыток
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/').read()" || exit 1
# Запускаем Gunicorn (по умолчанию, если не переопределено в docker-compose) на три воркера, привязывая его к # Переходим в директорию с manage.py для корректного запуска gunicorn
# порту 8000 и указывая на точку входа приложения (wsgi.py). WORKDIR /home/app/web/dicquo
CMD ["gunicorn", "--workers", "3", "--bind", "0.0.0.0:8000", "dicquo.wsgi:application"]
# Команда запуска
CMD ["gunicorn", "--workers", "3", "--bind", "0.0.0.0:8000", "dicquo.wsgi:application"]

45
PLANS.md Normal file
View File

@@ -0,0 +1,45 @@
# Планы по развитию проекта (DicQuo)
## 1. Список Авторов (Feature: Authors List)
**Цель:** Улучшить SEO (плоская структура) и навигацию, сохранив "Дзен" (минимализм).
**Концепция:**
- Добавить иконку "Люди/Авторы" в шапку сайта (рядом с бургером).
- По клику открывается **полноэкранный оверлей** (как статистика).
- Внутри список авторов карточками/строками.
**Элементы списка:**
1. **Имя Автора** (крупно) -> Ссылка на ротацию цитат автора (`/?tag=author-slug`).
2. **Счетчик цитат** (мелко, например `(25)`) -> Клик раскрывает "гармошку" (аккордеон).
3. **Список цитат** (внутри гармошки) -> Прямые ссылки на цитаты (например: `/123_nachalo-frazy...`). Текст ссылок — начало фразы.
**Техническая реализация:**
- **Backend:** `Context Processor` или логика в `IndexView` (или отдельный AJAX endpoint) для сбора данных:
```json
[
{
"name": "Имя",
"slug": "slug",
"count": 25,
"quotes": [{"id": 1, "url": "...", "text": "Текст..."}, ...]
}, ...
]
```
- **Frontend:** HTML/CSS для модального окна и JS для раскрытия списков.
## 2. Админка
- Починить мелкие баги в управлении тегами.
- Улучшить управление настройками типографа (etpgrf) через виртуальные поля.
- Поля в админке для настройки (кавычки, неразрывные пробелы и т.д.).
- При сохранении применять типограф к полям `szContent` -> `szContentHTML`.
- `szContentHTML` сделать редактитруемым чекрез CodeMirror (для ручной типографики тяжёлых случаев).
## 3. SEO и Оптимизация
- Проверить индексацию новых страниц `static_404`/`static_500`.
- Убедиться, что `canonical` ссылки работают корректно.
## 4. Дальние планы
- Форма для добавления цитат пользователями (с модерацией).
- API для интеграции с внешними сервисами (магазинами грампластинок и музыкальными сервисами).
- Сбор цитат из открытых источников (например, с помощью парсинга сайтов с цитатами или API).

View File

@@ -29,7 +29,7 @@ upstream dq-django {
# 2. Конфигурируем сервер # 2. Конфигурируем сервер
server { server {
server_name dq.cube2.ru dq2.cube2.ru; # Основное доменное имя server_name dq.cube2.ru; # Основное доменное имя
# Слушаем 80 порт (Certbot потом добавит сюда редирект на 443 и настройки SSL) # Слушаем 80 порт (Certbot потом добавит сюда редирект на 443 и настройки SSL)
listen 80; listen 80;
@@ -76,18 +76,24 @@ server {
} }
# --- СТРАНИЦЫ ОШИБОК (Custom Error Pages) --- # --- СТРАНИЦЫ ОШИБОК (Custom Error Pages) ---
# Если Django упал (502) или файл из media не найден Nginx-ом (404), показываем наши красивые заглушки. # Если Django упал (502) или сработал тайм-аут (504), Nginx должен отдать статический HTML.
# Файлы копируются в media/errors при старте контейнера. # Эти файлы должны лежать в папке, доступной Nginx (например, в media/errors).
# ТРЕБУЕТСЯ ЗАМЕНА ПРИ ДЕПЛОЕ: /home/user/app/dq-site -> ваш реальный путь #
error_page 404 /404.html; # ВАЖНО:
error_page 500 502 503 504 /500.html; # 1. Файлы 50x.html (500, 502, 503, 504) копируются в media/errors при старте контейнера (см. docker-compose.prod.yml -> command).
# 2. error_page директива перехватывает ошибки от апстрима (Gunicorn).
location = /404.html { error_page 500 502 503 504 /500.html;
# (Опционально) 404 тоже можно кастомизировать, но обычно Django сам отдает 404.
# Nginx отдаст эту страницу только если сам не найдет статику.
error_page 404 /404.html;
location = /500.html {
root /home/user/app/dq-site/media/errors; root /home/user/app/dq-site/media/errors;
internal; internal;
} }
location = /500.html { location = /404.html {
root /home/user/app/dq-site/media/errors; root /home/user/app/dq-site/media/errors;
internal; internal;
} }
@@ -102,7 +108,14 @@ server {
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Если нужно чтобы Django обрабатывал и HTTP, и HTTPS, то можно раскомментировать эту строку
# и передавать реальный протокол от клиента
# proxy_set_header X-Forwarded-Proto $scheme;
# Явно указываем https, потому что клиент всегда приходит по HTTPS к Nginx
# Даже если внутри контейнера это HTTP на 127.0.0.1:8010, для Django это должно быть HTTPS
proxy_set_header X-Forwarded-Proto https;
# Тайм-ауты (важно для долгих операций, если они есть) # Тайм-ауты (важно для долгих операций, если они есть)
proxy_read_timeout 180s; proxy_read_timeout 180s;

Binary file not shown.

View File

@@ -58,6 +58,7 @@ INSTALLED_APPS: list[str] = [
'django.contrib.sites', 'django.contrib.sites',
'django.contrib.sitemaps', 'django.contrib.sitemaps',
'taggit.apps.TaggitAppConfig', 'taggit.apps.TaggitAppConfig',
'django_select2',
'web.apps.WebConfig', 'web.apps.WebConfig',
] ]
@@ -78,6 +79,13 @@ DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR.parent / 'database/db.sqlite3', 'NAME': BASE_DIR.parent / 'database/db.sqlite3',
'OPTIONS': {
# Таймаут ожидания блокировки SQLite (в секундах)
# ВАЖНО: Увеличен до 60 сек для работы с несколькими воркерами Gunicorn
'timeout': 60,
# Дополнительные опции для лучшей работы SQLite при concurrent доступе
'init_command': "PRAGMA journal_mode=WAL;", # Write-Ahead Logging для лучшей concurrency
},
} }
} }
@@ -148,3 +156,12 @@ if not DEBUG:
WHITENOISE_ROOT = BASE_DIR.parent / 'public' WHITENOISE_ROOT = BASE_DIR.parent / 'public'
SITE_ID = 1 SITE_ID = 1
# Настройки безопасности для работы за прокси
if not DEBUG:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
USE_X_FORWARDED_HOST = True
USE_X_FORWARDED_PORT = True

View File

@@ -15,7 +15,7 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import path, re_path from django.urls import path, re_path, include
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib.sitemaps.views import sitemap from django.contrib.sitemaps.views import sitemap
from django.views.generic import TemplateView from django.views.generic import TemplateView
@@ -33,6 +33,7 @@ urlpatterns = [
re_path(r'^$', views.IndexView.as_view()), re_path(r'^$', views.IndexView.as_view()),
re_path(r'^(?P<dq_id>\d{1,12})_\S*$', views.DictumDetailView.as_view()), re_path(r'^(?P<dq_id>\d{1,12})_\S*$', views.DictumDetailView.as_view()),
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'), path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
path("select2/", include("django_select2.urls")),
] ]
if settings.DEBUG: if settings.DEBUG:
@@ -42,4 +43,7 @@ if settings.DEBUG:
path('500/', TemplateView.as_view(template_name="500.html")), path('500/', TemplateView.as_view(template_name="500.html")),
path('403/', TemplateView.as_view(template_name="403.html")), path('403/', TemplateView.as_view(template_name="403.html")),
path('400/', TemplateView.as_view(template_name="400.html")), path('400/', TemplateView.as_view(template_name="400.html")),
# Для проверки статических страниц ошибок (Nginx)
path('static_404/', TemplateView.as_view(template_name="static_404.html")),
path('static_500/', TemplateView.as_view(template_name="static_500.html")),
] ]

View File

@@ -1,31 +1,44 @@
{% extends "base.html" %} <!DOCTYPE html>
<html lang="ru">
{% block Title %}400: Плохой запрос{% endblock %} <head>
<meta charset="utf-8">
{% block CONTENT %}{% include "blocks/header_nav.html" %} <meta name="viewport" content="width=device-width, initial-scale=1">
<div class="container main-content"> <title>400: Плохой запрос | DicQuo</title>
<!-- Осно<D0BD><D0BE>ной контент: Текст + Картинка --> <style>
<div class="content-row"> body { margin: 0; min-height: 100vh; background-color: #211; color: silver; font-family: serif; opacity: 1; transition: opacity 0.9s ease-in-out; }
header { display: flex; justify-content: space-between; align-items: center; padding: 1vh 4vw; }
<!-- Текстовая колонка --> header > #logo { margin-top: 1vh; float: left; }
<div class="text-col"> header > #logo a { border: none; text-decoration: none; color: silver;}
<!-- Цитата --> main { padding: 1vh 8vw; display: flex; flex-direction: column; justify-content: center; min-height: 60vh; }
<blockquote id="bb" style="border:none; margin:0; padding:0;"> main > article { display: flex; align-items: center; justify-content: center; gap: 2vw; }
<span style="margin-left:-0.44em;">&laquo;</span>Вы спрашиваете меня о&nbsp;чем-то странном. Я&nbsp;не&nbsp;понимаю ваш запрос.» main > article > figure > p { color: silver; font-size: 3vmin; line-height: 3.5vmin; padding-bottom: 2vmin; font-style: italic; }
</blockquote> main > article > figure > blockquote { color: whitesmoke; font-size: 4.5vmin; line-height: 5vmin; border:none; margin:0; padding:0; }
main > article > figure > cite { color: silver; font-size: 3.5vmin; line-height: 4vmin; text-align: right; padding-top: 8vmin; font-style: italic; display: block; }
<!-- Автор --> .tags { text-align: center; color: silver; font-size: 1.5vmin; line-height: 1.9vmin; padding: 1vh 8vw; float: left;}
<div id="author"> .tags a { text-decoration: none; position: relative; padding: 0 0.5ex; display: inline-block; color: white; border-bottom: dotted 1px silver; background: linear-gradient(to right, rgba(255, 255, 255, 0.9) 40%, slategray, silver, lightyellow 50%, rgba(255, 255, 255, 0.4)); background-clip: initial; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 250% 100%; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; background-position: 100%; transition: background-position 0.65s ease; margin-right: 2vmin; }
<cite>Озадаченный Сервер (400)</cite> .tags a:hover { color: white; background-position: 0 100%; border-bottom: solid 1px white; }
</div> </style>
</div> </head>
<body>
</div> <header>
<div id="logo">
<!-- Блок тегов и навигации --> <a href="/" style="font-size: 3em; font-weight: bold; color: orange; font-style: italic">DQ</a>
<div class="tags">
<a href="/">Сформулировать иначе (на главную)</a>
</div> </div>
</header>
<main>
<article>
<figure>
<p>Загадочно:</p>
<blockquote id="bb">
<span style="margin-left:-0.44em;">&laquo;</span>Вы спрашиваете меня о&nbsp;чем-то странном. Я&nbsp;не&nbsp;понимаю
ваш запрос.»
</blockquote>
<cite>Озадаченный Сервер (400)</cite>
</figure>
</article>
</main>
<div class="tags">
<a href="/">Сформулировать иначе (на главную)</a>
</div> </div>
</body>
{% endblock %} </html>

View File

@@ -1,31 +1,43 @@
{% extends "base.html" %} <!DOCTYPE html>
<html lang="ru">
{% block Title %}403: Доступ запрещен{% endblock %} <head>
<meta charset="utf-8">
{% block CONTENT %}{% include "blocks/header_nav.html" %} <meta name="viewport" content="width=device-width, initial-scale=1">
<div class="container main-content"> <title>403: Доступ запрещен | DicQuo</title>
<!-- Основной контент: Текст + Картинка --> <style>
<div class="content-row"> body { margin: 0; min-height: 100vh; background-color: #211; color: silver; font-family: serif; opacity: 1; transition: opacity 0.9s ease-in-out; }
header { display: flex; justify-content: space-between; align-items: center; padding: 1vh 4vw; }
<!-- Текстовая колонка --> header > #logo { margin-top: 1vh; float: left; }
<div class="text-col"> header > #logo a { border: none; text-decoration: none; color: silver;}
<!-- Цитата --> main { padding: 1vh 8vw; display: flex; flex-direction: column; justify-content: center; min-height: 60vh; }
<blockquote id="bb" style="border:none; margin:0; padding:0;"> main > article { display: flex; align-items: center; justify-content: center; gap: 2vw; }
<span style="margin-left:-0.44em;">&laquo;</span>Вам сюда нельзя. Даже если очень хочется. Уходите!» main > article > figure > p { color: silver; font-size: 3vmin; line-height: 3.5vmin; padding-bottom: 2vmin; font-style: italic; }
</blockquote> main > article > figure > blockquote { color: whitesmoke; font-size: 4.5vmin; line-height: 5vmin; border:none; margin:0; padding:0; }
main > article > figure > cite { color: silver; font-size: 3.5vmin; line-height: 4vmin; text-align: right; padding-top: 8vmin; font-style: italic; display: block; }
<!-- Автор --> .tags { text-align: center; color: silver; font-size: 1.5vmin; line-height: 1.9vmin; padding: 1vh 8vw; float: left;}
<div id="author"> .tags a { text-decoration: none; position: relative; padding: 0 0.5ex; display: inline-block; color: white; border-bottom: dotted 1px silver; background: linear-gradient(to right, rgba(255, 255, 255, 0.9) 40%, slategray, silver, lightyellow 50%, rgba(255, 255, 255, 0.4)); background-clip: initial; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 250% 100%; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; background-position: 100%; transition: background-position 0.65s ease; margin-right: 2vmin; }
<cite>Строгий Вахтёр (403)</cite> .tags a:hover { color: white; background-position: 0 100%; border-bottom: solid 1px white; }
</div> </style>
</div> </head>
<body>
</div> <header>
<div id="logo">
<!-- Блок тегов и навигации --> <a href="/" style="font-size: 3em; font-weight: bold; color: orange; font-style: italic">DQ</a>
<div class="tags">
<a href="/">Уйти по-добру по-здорову</a>
</div> </div>
</header>
<main>
<article>
<figure>
<p>Категорически:</p>
<blockquote id="bb">
<span style="margin-left:-0.44em;">&laquo;</span>Вам сюда нельзя. Даже если очень хочется. Уходите!»
</blockquote>
<cite>Строгий Вахтёр (403)</cite>
</figure>
</article>
</main>
<div class="tags">
<a href="/">Уйти по-добру по-здорову</a>
</div> </div>
</body>
{% endblock %} </html>

View File

@@ -1,31 +1,43 @@
{% extends "base.html" %} <!DOCTYPE html>
<html lang="ru">
{% block Title %}404: Страница не найдена{% endblock %} <head>
<meta charset="utf-8">
{% block CONTENT %}{% include "blocks/header_nav.html" %} <meta name="viewport" content="width=device-width, initial-scale=1">
<div class="container main-content"> <title>404: Страница не найдена | DicQuo</title>
<!-- Основной контент: Текст + Картинка --> <style>
<div class="content-row"> body { margin: 0; min-height: 100vh; background-color: #211; color: silver; font-family: serif; opacity: 1; transition: opacity 0.9s ease-in-out; }
header { display: flex; justify-content: space-between; align-items: center; padding: 1vh 4vw; }
<!-- Текстовая колонка --> header > #logo { margin-top: 1vh; float: left; }
<div class="text-col"> header > #logo a { border: none; text-decoration: none; color: silver;}
<!-- Цитата --> main { padding: 1vh 8vw; display: flex; flex-direction: column; justify-content: center; min-height: 60vh; }
<blockquote id="bb" style="border:none; margin:0; padding:0;"> main > article { display: flex; align-items: center; justify-content: center; gap: 2vw; }
<span style="margin-left:-0.44em;">&laquo;</span>Я искал везде. Даже под&nbsp;диваном. Этой страницы здесь&nbsp;нет.» main > article > figure > p { color: silver; font-size: 3vmin; line-height: 3.5vmin; padding-bottom: 2vmin; font-style: italic; }
</blockquote> main > article > figure > blockquote { color: whitesmoke; font-size: 4.5vmin; line-height: 5vmin; border:none; margin:0; padding:0; }
main > article > figure > cite { color: silver; font-size: 3.5vmin; line-height: 4vmin; text-align: right; padding-top: 8vmin; font-style: italic; display: block; }
<!-- Автор --> .tags { text-align: center; color: silver; font-size: 1.5vmin; line-height: 1.9vmin; padding: 1vh 8vw; float: left;}
<div id="author"> .tags a { text-decoration: none; position: relative; padding: 0 0.5ex; display: inline-block; color: white; border-bottom: dotted 1px silver; background: linear-gradient(to right, rgba(255, 255, 255, 0.9) 40%, slategray, silver, lightyellow 50%, rgba(255, 255, 255, 0.4)); background-clip: initial; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 250% 100%; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; background-position: 100%; transition: background-position 0.65s ease; margin-right: 2vmin; }
<cite>Системный Администратор (404)</cite> .tags a:hover { color: white; background-position: 0 100%; border-bottom: solid 1px white; }
</div> </style>
</div> </head>
<body>
</div> <header>
<div id="logo">
<!-- Блок тегов и навигации --> <a href="/" style="font-size: 3em; font-weight: bold; color: orange; font-style: italic">DQ</a>
<div class="tags">
<a href="/">Вернуться на главную</a>
</div> </div>
</header>
<main>
<article>
<figure>
<p>Вздыхая:</p>
<blockquote id="bb">
<span style="margin-left:-0.44em;">&laquo;</span>Я искал везде. Даже под&nbsp;диваном. Этой страницы здесь&nbsp;нет.»
</blockquote>
<cite>Системный Администратор (404)</cite>
</figure>
</article>
</main>
<div class="tags">
<a href="/">Вернуться на главную</a>
</div> </div>
</body>
{% endblock %} </html>

View File

@@ -1,31 +1,44 @@
{% extends "base.html" %} <!DOCTYPE html>
<html lang="ru">
{% block Title %}500: Ошибка сервера{% endblock %} <head>
<meta charset="utf-8">
{% block CONTENT %}{% include "blocks/header_nav.html" %} <meta name="viewport" content="width=device-width, initial-scale=1">
<div class="container main-content"> <title>500: Ошибка сервера DicQuo</title>
<!-- Основной контент: Текст + Картинка --> <style>
<div class="content-row"> body { margin: 0; min-height: 100vh; background-color: #211; color: silver; font-family: serif; opacity: 1; transition: opacity 0.9s ease-in-out; }
header { display: flex; justify-content: space-between; align-items: center; padding: 1vh 4vw; }
<!-- Текстовая колонка --> header > #logo { margin-top: 1vh; float: left; }
<div class="text-col"> header > #logo a { border: none; text-decoration: none; color: silver;}
<!-- Цитата --> main { padding: 1vh 8vw; display: flex; flex-direction: column; justify-content: center; min-height: 60vh; }
<blockquote id="bb" style="border:none; margin:0; padding:0;"> main > article { display: flex; align-items: center; justify-content: center; gap: 2vw; }
<span style="margin-left:-0.44em;">&laquo;</span>Что-то пошло не&nbsp;так. Кажется, я&nbsp;уронил сервер. Подождите, пока я&nbsp;его подниму.» main > article > figure > p { color: silver; font-size: 3vmin; line-height: 3.5vmin; padding-bottom: 2vmin; font-style: italic; }
</blockquote> main > article > figure > blockquote { color: whitesmoke; font-size: 4.5vmin; line-height: 5vmin; border:none; margin:0; padding:0; }
main > article > figure > cite { color: silver; font-size: 3.5vmin; line-height: 4vmin; text-align: right; padding-top: 8vmin; font-style: italic; display: block; }
<!-- Автор --> .tags { text-align: center; color: silver; font-size: 1.5vmin; line-height: 1.9vmin; padding: 1vh 8vw; float: left;}
<div id="author"> .tags a { text-decoration: none; position: relative; padding: 0 0.5ex; display: inline-block; color: white; border-bottom: dotted 1px silver; background: linear-gradient(to right, rgba(255, 255, 255, 0.9) 40%, slategray, silver, lightyellow 50%, rgba(255, 255, 255, 0.4)); background-clip: initial; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 250% 100%; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; background-position: 100%; transition: background-position 0.65s ease; margin-right: 2vmin; }
<cite>Системный Администратор (500)</cite> .tags a:hover { color: white; background-position: 0 100%; border-bottom: solid 1px white; }
</div> </style>
</div> </head>
<body>
</div> <header>
<div id="logo">
<!-- Блок тегов и навигации --> <a href="/" style="font-size: 3em; font-weight: bold; color: orange; font-style: italic">DQ</a>
<div class="tags">
<a href="/">Попробовать обновить страницу</a>
</div> </div>
</header>
<main>
<article>
<figure>
<p>Неожиданно:</p>
<blockquote id="bb">
<span style="margin-left:-0.44em;">&laquo;</span>Что-то пошло не&nbsp;так. Кажется, я&nbsp;уронил сервер.
Подождите, пока я&nbsp;его подниму.»
</blockquote>
<cite>Системный Администратор (500)</cite>
</figure>
</article>
</main>
<div class="tags">
<a href="/">Попробовать обновить страницу</a>
</div> </div>
</body>
{% endblock %} </html>

View File

@@ -12,13 +12,18 @@
<meta property="og:title" content="{% block OgTitle %}{{ DQ.szContent|truncatechars:85 }}{% endblock %}"/> <meta property="og:title" content="{% block OgTitle %}{{ DQ.szContent|truncatechars:85 }}{% endblock %}"/>
<meta property="og:description" content="{% block OgDescription %}{{ DQ.szIntro|default:'' }} {{ DQ.szContent }} {{ AUTHOR.szAuthor|default:'' }}{% endblock %}"/> <meta property="og:description" content="{% block OgDescription %}{{ DQ.szIntro|default:'' }} {{ DQ.szContent }} {{ AUTHOR.szAuthor|default:'' }}{% endblock %}"/>
<meta property="og:url" content="{{ request.build_absolute_uri }}"/> <meta property="og:url" content="{{ request.build_absolute_uri }}"/>
<meta property="og:site_name" content="DicQuo"/> <meta property="og:site_name" content="DicQuo"/>{% if IMAGE %}
{% if IMAGE %}<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{{ IMAGE.url }}"/>{% endif %} <meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{{ IMAGE.url }}"/>{% endif %}
{# Шутка #}<meta name="generator" content="Microsoft FrontPage 1.0"/> {# Шутка #}<meta name="generator" content="Microsoft FrontPage 1.0"/>
{# Canonical #}<link rel="canonical" href="{{ request.build_absolute_uri }}"/> {# Canonical #}{% if DQ and DQ_SLUG %}<link rel="canonical" href="{{ request.scheme }}://{{ request.get_host }}/{{ DQ.id }}_{{ DQ_SLUG }}"/>{% else %}<link rel="canonical" href="{{ request.build_absolute_uri }}"/>{% endif %}
{# Favicons #}<link rel="shortcut icon" type="image/x-icon" href="{% static 'img/favicon.ico' %}"/> {# Favicons #}<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="icon" type="image/png" href="{% static 'img/favicon.png' %}"/> {# Favicons #}<link rel="icon" type="image/png" href="/favicon.png"/>
{# Favicons #}<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
{# Favicons #}<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico"/>
{# Favicons #}<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
{# Favicons #}<link rel="manifest" href="/site.webmanifest" />
{# Technical Meta #}<meta http-equiv="Last-Modified" content="{% block Last4Meta %}{% endblock %}"/> {# Technical Meta #}<meta http-equiv="Last-Modified" content="{% block Last4Meta %}{% endblock %}"/>
{# Для ИИ #}<link rel="help" type="text/markdown" href="/llms.txt"/>
{# CSS #}<link rel="stylesheet" href="{% static 'css/dicquo.css' %}"/> {# CSS #}<link rel="stylesheet" href="{% static 'css/dicquo.css' %}"/>
<noscript><style>body { opacity: 1; }</style></noscript>{# Показать все если JS не поддерживатся #} <noscript><style>body { opacity: 1; }</style></noscript>{# Показать все если JS не поддерживатся #}
{% block ExtraHead %}{# Если нужно что=то добавить в `<head>` #}{% endblock %} {% block ExtraHead %}{# Если нужно что=то добавить в `<head>` #}{% endblock %}

View File

@@ -1,2 +1,2 @@
{% load static %}<script src="{% static 'js/counters.js' %}"></script> {% load static %}<script src="{% static 'js/counters.js' %}"></script>
<noscript><div><img src="https://top-fwz1.mail.ru/counter?id=3744288;js=na" class="counter-pixel" alt="Top.Mail.Ru"/></div></noscript> <noscript><div><img src="https://top-fwz1.mail.ru/counter?id=3744288;js=na" class="counter-pixel" alt=""/></div><div><img src="https://mc.yandex.ru/watch/106953063" class="counter-pixel" alt="" /></div></noscript>

View File

@@ -1,27 +1,43 @@
<!DOCTYPE html> <!DOCTYPE html>
</html>
</body>
</div>
<a href="/">Вернуться на главную</a>
<cite>Системный Администратор (404)</cite>
</blockquote>
<span>«</span>Я искал везде. Даже под&nbsp;диваном. Этой страницы здесь&nbsp;нет.»
<blockquote>
<div class="container">
<body>
</head>
</style>
a:hover { color: #999; border-bottom: 1px solid #999; }
a { color: #555; text-decoration: none; border-bottom: 1px dotted #555; transition: color 0.3s; font-size: 0.8em; margin-top: 30px; display: inline-block;}
cite { display: block; font-size: 0.9em; color: #777; margin-top: 15px; font-style: normal;}
blockquote span { margin-left: -0.44em; }
blockquote { font-size: 2em; margin: 0 0 20px 0; font-style: italic; line-height: 1.4; }
.container { max-width: 600px; padding: 20px; }
body { background-color: #111; color: #ccc; font-family: Georgia, serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; text-align: center; }
<style>
<title>404: Страница не найдена</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<head>
<html lang="ru"> <html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>404: Страница не найдена | DicQuo</title>
<style>
body { margin: 0; min-height: 100vh; background-color: #211; color: silver; font-family: serif; opacity: 1; transition: opacity 0.9s ease-in-out; }
header { display: flex; justify-content: space-between; align-items: center; padding: 1vh 4vw; }
header > #logo { margin-top: 1vh; float: left; }
header > #logo a { border: none; text-decoration: none; color: silver;}
main { padding: 1vh 8vw; display: flex; flex-direction: column; justify-content: center; min-height: 60vh; }
main > article { display: flex; align-items: center; justify-content: center; gap: 2vw; }
main > article > figure > p { color: silver; font-size: 3vmin; line-height: 3.5vmin; padding-bottom: 2vmin; font-style: italic; }
main > article > figure > blockquote { color: whitesmoke; font-size: 4.5vmin; line-height: 5vmin; border:none; margin:0; padding:0; }
main > article > figure > cite { color: silver; font-size: 3.5vmin; line-height: 4vmin; text-align: right; padding-top: 8vmin; font-style: italic; display: block; }
.tags { text-align: center; color: silver; font-size: 1.5vmin; line-height: 1.9vmin; padding: 1vh 8vw; float: left;}
.tags a { text-decoration: none; position: relative; padding: 0 0.5ex; display: inline-block; color: white; border-bottom: dotted 1px silver; background: linear-gradient(to right, rgba(255, 255, 255, 0.9) 40%, slategray, silver, lightyellow 50%, rgba(255, 255, 255, 0.4)); background-clip: initial; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 250% 100%; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; background-position: 100%; transition: background-position 0.65s ease; margin-right: 2vmin; }
.tags a:hover { color: white; background-position: 0 100%; border-bottom: solid 1px white; }
</style>
</head>
<body>
<header>
<div id="logo">
<a href="/" style="font-size: 3em; font-weight: bold; color: orange; font-style: italic">DQ</a>
</div>
</header>
<main>
<article>
<figure>
<p>Озадачено:</p>
<blockquote id="bb">
<span style="margin-left:-0.44em;">&laquo;</span>Я искал везде. Даже под&nbsp;диваном. Этой страницы здесь&nbsp;нет.»
</blockquote>
<cite>Системный Администратор (404)</cite>
</figure>
</article>
</main>
<div class="tags">
<a href="/">Вернуться на главную</a>
</div>
</body>
</html>

View File

@@ -1,27 +1,45 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru"> <html lang="ru">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>500: Ошибка сервера</title> <title>500: Ошибка сервера | DicQuo</title>
<style> <style>
body { background-color: #111; color: #ccc; font-family: Georgia, serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; text-align: center; } body { margin: 0; min-height: 100vh; background-color: #211; color: silver; font-family: serif; opacity: 1; transition: opacity 0.9s ease-in-out; }
.container { max-width: 600px; padding: 20px; } header { display: flex; justify-content: space-between; align-items: center; padding: 1vh 4vw; }
blockquote { font-size: 2em; margin: 0 0 20px 0; font-style: italic; line-height: 1.4; } header > #logo { margin-top: 1vh; float: left; }
blockquote span { margin-left: -0.44em; } header > #logo a { border: none; text-decoration: none; color: silver;}
cite { display: block; font-size: 0.9em; color: #777; margin-top: 15px; font-style: normal; } main { padding: 1vh 8vw; display: flex; flex-direction: column; justify-content: center; min-height: 60vh; }
a { color: #555; text-decoration: none; border-bottom: 1px dotted #555; transition: color 0.3s; font-size: 0.8em; margin-top: 30px; display: inline-block;} main > article { display: flex; align-items: center; justify-content: center; gap: 2vw; }
a:hover { color: #999; border-bottom: 1px solid #999; } main > article > figure > p { color: silver; font-size: 3vmin; line-height: 3.5vmin; padding-bottom: 2vmin; font-style: italic; }
</style> main > article > figure > blockquote { color: whitesmoke; font-size: 4.5vmin; line-height: 5vmin; border:none; margin:0; padding:0; }
main > article > figure > cite { color: silver; font-size: 3.5vmin; line-height: 4vmin; text-align: right; padding-top: 8vmin; font-style: italic; display: block; }
.tags { text-align: center; color: silver; font-size: 1.5vmin; line-height: 1.9vmin; padding: 1vh 8vw; float: left;}
.tags a { text-decoration: none; position: relative; padding: 0 0.5ex; display: inline-block; color: white; border-bottom: dotted 1px silver; background: linear-gradient(to right, rgba(255, 255, 255, 0.9) 40%, slategray, silver, lightyellow 50%, rgba(255, 255, 255, 0.4)); background-clip: initial; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 250% 100%; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; background-position: 100%; transition: background-position 0.65s ease; margin-right: 2vmin; }
.tags a:hover { color: white; background-position: 0 100%; border-bottom: solid 1px white; }
</style>
</head> </head>
<body> <body>
<div class="container"> <header>
<blockquote> <div id="logo">
<span>«</span>Что-то пошло не&nbsp;так. Кажется, я&nbsp;уронил сервер, контейнер или &nbsp;случилось что-то ещё. Подождите, пока я&nbsp;его подниму.» <a href="/" style="font-size: 3em; font-weight: bold; color: orange; font-style: italic">DQ</a>
</blockquote> </div>
<cite>Системный Администратор (5xx)</cite> </header>
<a href="#" onclick="window.location.reload(); return false;">Попробовать обновить через 5&nbsp;минут.</a> <main>
</div> <article>
<figure>
<p>Неожиданно:</p>
<blockquote id="bb">
<span style="margin-left:-0.44em;">&laquo;</span>Что-то пошло не&nbsp;так. Кажется, я&nbsp;уронил сервер.
Подождите, пока я&nbsp;его подниму.»
</blockquote>
<cite>Системный Администратор (500)</cite>
</figure>
</article>
</main>
<div class="tags">
<a href="#" onclick="window.location.reload(); return false;">Попробовать обновить страницу</a>
</div>
</body> </body>
</html> </html>

View File

@@ -2,6 +2,13 @@
from django.contrib import admin from django.contrib import admin
from django import forms from django import forms
from web.models import TbDictumAndQuotes, TbAuthor, TbImages, TbOrigin from web.models import TbDictumAndQuotes, TbAuthor, TbImages, TbOrigin
from taggit.managers import TaggableManager
from django_select2.forms import Select2TagWidget
from taggit.models import Tag
from taggit.utils import parse_tags
from django.db import models
from django.db.utils import OperationalError, ProgrammingError
from django.utils.functional import lazy
try: try:
from etpgrf.typograph import Typographer from etpgrf.typograph import Typographer
@@ -18,6 +25,123 @@ except ImportError:
def __init__(self, **kwargs): pass def __init__(self, **kwargs): pass
class TagSelect2Widget(Select2TagWidget):
"""
Select2-виджет для django-taggit, работающий по ИМЕНАМ тегов.
- подхватывает уже сохранённые теги;
- показывает выпадающий список из существующих тегов;
- даёт создавать новые теги с пробелами в названии.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Определяем функцию, которая будет выполнена лениво.
def get_choices_safely():
try:
# list() материализует ленивый QuerySet, что важно для lazy()
return list(Tag.objects.values_list('name', 'name'))
except (OperationalError, ProgrammingError):
# Если таблицы нет (например, при collectstatic), возвращаем пустой список.
return []
# lazy() не выполняет функцию сразу, а создает "обещание" (promise),
lazy_safe_choices = lazy(get_choices_safely, list)
# choices: список всех существующих тегов по имени.
# Присваиваем ему ленивый объект.
self.choices = lazy_safe_choices()
# @property
# def choices(self):
# # Этот код будет выполняться только тогда,
# # когда Django реально запросит self.choices для отрисовки.
# # К этому моменту приложение будет полностью готово.
# try:
# self.choices = [(t.name, t.name) for t in Tag.objects.all()]
# except (OperationalError, ProgrammingError):
# self.choices = []
#
# # Важно: Нам нужно установить setter, даже если он пустой,
# # потому что родительский класс будет пытаться присвоить ему значение.
# @choices.setter
# def choices(self, value):
# pass
class Media:
css = {
"all": ("css/select2_taggit_admin.css",),
}
def build_attrs(self, base_attrs, extra_attrs=None):
"""
Настраиваем Select2 так, чтобы пробел НЕ разделял тег
на несколько частей (нужны теги с пробелами: «Сергей Курёхин»).
Оставляем в разделителях только запятую.
"""
attrs = super().build_attrs(base_attrs, extra_attrs)
# По умолчанию django-select2 ставит: [",", " "]
# Нам нужен только разделитель-запятая.
# Строка '[","]' — корректный JSON-массив из одного элемента.
# Важно: сюда нужно класть СТРОКУ с JSON-массивом, а не python-список.
# Иначе в HTML окажется "['[\", \"]', ...]" и Select2 будет вести себя непредсказуемо.
attrs["data-token-separators"] = '[","]'
return attrs
def format_value(self, value):
"""
Преобразуем значение из TaggableManager/TagField
в список ИМЁН тегов, который ожидает Select2TagWidget.
"""
from django.db.models import QuerySet
if value is None:
return []
# QuerySet или список Tag-объектов
if isinstance(value, QuerySet):
return [t.name for t in value]
if isinstance(value, (list, tuple, set)):
names = []
for v in value:
if isinstance(v, Tag):
names.append(v.name)
else:
names.append(str(v))
return names
# Строка вида "tag1, tag2" — разбираем в список имён
if isinstance(value, str):
return parse_tags(value)
return super().format_value(value)
def value_from_datadict(self, data, files, name):
"""
Django-Select2 возвращает список значений (['Сергей Курёхин', 'Другой тег']).
Taggit (TagField) ждёт ОДНУ строку, которую потом парсит в список тегов.
Если отдать список, он превратится в строку `"['Сергей', 'Курёхин']"`,
и распарсится в кривые теги — этого мы избегаем.
"""
values = super().value_from_datadict(data, files, name)
if not values:
return ""
# Для нашего виджета value — это уже список имён тегов
tag_names = [str(v).strip() for v in values if str(v).strip()]
if not tag_names:
return ""
# ОДИН многословный тег: "Сергей Курёхин" -> "Сергей Курёхин,"
# Тогда parse_tags переключится в режим "деление по запятым"
if len(tag_names) == 1:
single = tag_names[0]
if " " in single and "," not in single and '"' not in single:
return single + ","
return single
# Несколько тегов — явная запятая между ними.
return ", ".join(tag_names)
class DictumAdminForm(forms.ModelForm): class DictumAdminForm(forms.ModelForm):
# Виртуальные поля для настройки типографа # Виртуальные поля для настройки типографа
etp_language = forms.ChoiceField( etp_language = forms.ChoiceField(
@@ -34,7 +158,7 @@ class DictumAdminForm(forms.ModelForm):
) )
etp_hanging_punctuation = forms.ChoiceField( etp_hanging_punctuation = forms.ChoiceField(
label="Висячая пунктуация", label="Висячая пунктуация",
choices=[('no', 'Нет'), ('left', 'Слева'), ('right', 'Справа'), ('both', 'Обе стороны')], choices=[('no', 'Нет'), ('left', 'Слева'), ('right', 'Справа'), ],
initial='left', initial='left',
required=False, required=False,
help_text="Выносить кавычки за границу текстового блока" help_text="Выносить кавычки за границу текстового блока"
@@ -43,7 +167,7 @@ class DictumAdminForm(forms.ModelForm):
label="Переносы", label="Переносы",
initial=True, initial=True,
required=False, required=False,
help_text="Расставлять мягкие переносы (&amp;shy;)" help_text="Расставлять мягкие переносы (<tt>&amp;shy;</tt>)"
) )
etp_sanitize = forms.BooleanField( etp_sanitize = forms.BooleanField(
label="Санитайзер (HTML)", label="Санитайзер (HTML)",
@@ -62,6 +186,9 @@ class DictumAdminForm(forms.ModelForm):
class Meta: class Meta:
model = TbDictumAndQuotes model = TbDictumAndQuotes
fields = '__all__' fields = '__all__'
widgets = {
'tags': TagSelect2Widget,
}
# Register your models here. # Register your models here.
@@ -83,6 +210,7 @@ class AdmDictumAndQuotesAdmin(admin.ModelAdmin):
('Настройки типографа (Etpgrf)', { ('Настройки типографа (Etpgrf)', {
'classes': ('collapse',), 'classes': ('collapse',),
'fields': ( 'fields': (
('bTypograph',),
('etp_language', 'etp_mode'), ('etp_language', 'etp_mode'),
('etp_quotes', 'etp_sanitize'), ('etp_quotes', 'etp_sanitize'),
('etp_hyphenation', 'etp_hanging_punctuation'), ('etp_hyphenation', 'etp_hanging_punctuation'),
@@ -95,11 +223,15 @@ class AdmDictumAndQuotesAdmin(admin.ModelAdmin):
}), }),
('Служебное', { ('Служебное', {
'classes': ('collapse',), 'classes': ('collapse',),
'fields': ('iViewCounter', 'imFileOG', 'bTypograph') # bTypograph kept for compatibility 'fields': ('iViewCounter', 'imFileOG', ) # bTypograph kept for compatibility
}) })
) )
readonly_fields = ('szIntroHTML', 'szContentHTML', 'iViewCounter') readonly_fields = ('szIntroHTML', 'szContentHTML', 'iViewCounter')
formfield_overrides = {
models.ManyToManyField: {'widget': Select2TagWidget},
}
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
# 1. Читаем базовые настройки # 1. Читаем базовые настройки
langs = form.cleaned_data.get('etp_language', 'ru').split(',') langs = form.cleaned_data.get('etp_language', 'ru').split(',')
@@ -199,6 +331,11 @@ class AdmImages(admin.ModelAdmin):
list_display_links = ('id', 'szCaption') list_display_links = ('id', 'szCaption')
empty_value_display = u"<b style='color:red;'>-empty-</b>" empty_value_display = u"<b style='color:red;'>-empty-</b>"
# Добавляем виджет для тегов
formfield_overrides = {
TaggableManager: {'widget': TagSelect2Widget},
}
def get_queryset(self, request): def get_queryset(self, request):
return super().get_queryset(request).prefetch_related('tags') return super().get_queryset(request).prefetch_related('tags')
@@ -212,6 +349,11 @@ class AdmAuthor(admin.ModelAdmin):
list_display_links = ('id', 'szAuthor') list_display_links = ('id', 'szAuthor')
empty_value_display = u"<b style='color:red;'>-empty-</b>" empty_value_display = u"<b style='color:red;'>-empty-</b>"
# Добавляем виджет для тегов
formfield_overrides = {
TaggableManager: {'widget': TagSelect2Widget},
}
def get_queryset(self, request): def get_queryset(self, request):
return super().get_queryset(request).prefetch_related('tags') return super().get_queryset(request).prefetch_related('tags')
@@ -223,4 +365,3 @@ admin.site.register(TbDictumAndQuotes, AdmDictumAndQuotesAdmin)
admin.site.register(TbOrigin, AdmOrigin) admin.site.register(TbOrigin, AdmOrigin)
admin.site.register(TbImages, AdmImages) admin.site.register(TbImages, AdmImages)
admin.site.register(TbAuthor, AdmAuthor) admin.site.register(TbAuthor, AdmAuthor)

View File

@@ -0,0 +1,17 @@
# Generated by Django 6.0.2 on 2026-03-19 20:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('web', '0003_alter_tbdictumandquotes_btypograph_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='tbdictumandquotes',
options={'ordering': ['-id'], 'verbose_name': 'ВЫСКАЗЫВАНИЕ', 'verbose_name_plural': 'ВЫСКАЗЫВАНИЯ'},
),
]

View File

@@ -16,6 +16,7 @@ import pytils
class RuTag(Tag): class RuTag(Tag):
class Meta: class Meta:
proxy = True proxy = True
# ordering = ['id']
def slugify(self, tag, i=None): def slugify(self, tag, i=None):
return pytils.translit.slugify(self.name.lower())[:128] return pytils.translit.slugify(self.name.lower())[:128]
@@ -24,6 +25,7 @@ class RuTag(Tag):
class RuTaggedItem(TaggedItem): class RuTaggedItem(TaggedItem):
class Meta: class Meta:
proxy = True proxy = True
# ordering = ['id']
@classmethod @classmethod
def tag_model(cls): def tag_model(cls):
@@ -108,7 +110,73 @@ class TbImages(models.Model):
# заменим имя файла картинки # заменим имя файла картинки
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.imFile.name = pytils.translit.slugify(self.szCaption.lower()) + str(Path(self.imFile.name).suffixes) import os
from django.conf import settings
old_obj = None
old_file_path = None
# Получаем старую запись, если она есть
if self.pk:
try:
old_obj = TbImages.objects.get(pk=self.pk)
# Пытаемся получить путь к файлу. Если файл не найден физически, Django может выкинуть ошибку здесь или позже
# Поэтому просто берем имя из БД и формируем путь руками, чтобы не зависеть от Storage
if old_obj.imFile:
old_file_path = os.path.join(settings.MEDIA_ROOT, str(old_obj.imFile.name))
except TbImages.DoesNotExist:
pass
# Fix 1: Если старый путь уже битый (содержит ['...'])
if old_file_path and "['" in old_file_path:
# Формируем "исправленный" путь (каким он должен быть)
corrected_path = old_file_path.replace("['", "").replace("']", "").replace("'", "")
# Проверяем: если битого файла нет, а исправленный есть -> значит БД врет
if not os.path.exists(old_file_path) and os.path.exists(corrected_path):
# Исправляем текущее имя файла в объекте (убираем мусор из имени)
self.imFile.name = str(self.imFile.name).replace("['", "").replace("']", "").replace("'", "")
# Обновляем переменную old_file_path, чтобы дальнейшая логика переименования работала корректно
old_file_path = corrected_path
# Получаем текущее имя и расширение (уже возможно исправленное выше)
current_path = Path(str(self.imFile.name))
current_suffix = current_path.suffix
# Fix 2: Чиним расширение еще раз (на всякий случай, если Fix 1 не сработал или это новый объект)
if "['" in str(current_suffix):
current_suffix = str(current_suffix).replace("['", "").replace("']", "").replace("'", "")
# Формируем новое имя файла на основе заголовка (Slug)
new_filename = pytils.translit.slugify(self.szCaption.lower()) + current_suffix
# Определяем папку (если есть родитель, используем его, иначе img2)
# Важно: self.imFile.name может содержать полный путь. Нам нужен только относительный от MEDIA_ROOT
# Но проще взять родителя из текущего имени
parent_dir = current_path.parent.name if current_path.parent.name else 'img2'
new_name_with_path = str(Path(parent_dir) / new_filename)
# Переименование физического файла
# Сравниваем старое имя (из БД) с новым (сгенерированным)
if old_obj and str(old_obj.imFile.name) != new_name_with_path:
new_file_full_path = os.path.join(settings.MEDIA_ROOT, new_name_with_path)
# Если старый файл (old_file_path) существует физически, переименовываем его
if old_file_path and os.path.exists(old_file_path):
try:
os.makedirs(os.path.dirname(new_file_full_path), exist_ok=True)
os.rename(old_file_path, new_file_full_path)
self.imFile.name = new_name_with_path
except OSError as e:
print(f"Error renaming file from {old_file_path} to {new_file_full_path}: {e}")
else:
# Если старого файла нет, просто обновляем имя в БД
self.imFile.name = new_name_with_path
else:
# Если имя не менялось или объекта не было, просто устанавливаем правильное имя
# (например, чтобы убрать мусор из расширения в БД)
self.imFile.name = new_name_with_path
super(TbImages, self).save(*args, **kwargs) super(TbImages, self).save(*args, **kwargs)
class Meta: class Meta:

View File

@@ -79,6 +79,8 @@ class CommonContextMixin:
request.session['seen_ids'] = seen_ids request.session['seen_ids'] = seen_ids
context.update({'DQ': dq}) context.update({'DQ': dq})
# slag текущей цитаты
context.update({"DQ_SLUG": pytils.translit.slugify(dq.szContent.lower()[:120])})
# --- 3. АВТОР И ТЕГИ --- # --- 3. АВТОР И ТЕГИ ---
try: try:

View File

@@ -5,7 +5,8 @@
# Вариант 2 (если оставили имя): docker compose -f docker-compose.prod.yml up -d # Вариант 2 (если оставили имя): docker compose -f docker-compose.prod.yml up -d
# ============================================================================== # ==============================================================================
version: '3.8' # В новой версии Docker не нужно
# version: '3.8'
services: services:
# --- ОCНОВНОЙ СЕРВИС: DJANGO + GUNICORN + WHITENOISE --- # --- ОCНОВНОЙ СЕРВИС: DJANGO + GUNICORN + WHITENOISE ---
@@ -27,25 +28,26 @@ services:
# 3. КОМАНДА ЗАПУСКА (Замена entrypoint.sh) # 3. КОМАНДА ЗАПУСКА (Замена entrypoint.sh)
# Выполняем цепочку команд внутри контейнера при запуске: # Выполняем цепочку команд внутри контейнера при запуске:
# 1. Миграции # a. Миграции
# 2. Collectstatic # b. Collectstatic
# 3. Создаем папку nginx в примонтированном томе конфигов (если нет) # с. Создаем папку nginx в примонтированном томе конфигов (если нет)
# 4. Копирование конфига Nginx с авто-заменой путей через sed (замену реального пути на хосте получаем через переменную окружения HOST_PROJECT_PATH) # d. Копирование конфига Nginx с авто-заменой путей через sed (замену реального пути на хосте получаем
# 5. Инициализация боевого конфига (если нет) # через переменную окружения HOST_PROJECT_PATH)
# 6. Создаем папку для ошибок и копируем туда статические страницы 404/500 # e. Инициализация боевого конфига (если нет)
# 7. Запуск Gunicorn # f. Создаем папку для ошибок и копируем туда статические страницы 404/500 (там их увидит Nginx хоста)
# g. Запуск Gunicorn
command: > command: >
sh -c "python manage.py migrate --noinput && sh -c "python manage.py migrate --noinput &&
python manage.py collectstatic --noinput && python manage.py collectstatic --noinput &&
mkdir -p /nginx_configs_host/nginx && mkdir -p /nginx_configs_host/nginx &&
sed \"s|/home/user/app/dq-site|${HOST_PROJECT_PATH:-/home/default_user/projects/dq-site}|g\" /app/configs/nginx/dq-app--external-nginx.conf > /nginx_configs_host/nginx/nginx_dq.conf.example && sed \"s|/home/user/app/dq-site|${HOST_PROJECT_PATH:-/home/default_user/projects/dq-site}|g\" /nginx_configs_host/nginx/dq-app--external-nginx.conf > /nginx_configs_host/nginx/nginx_dq.conf.example &&
if [ ! -f /nginx_configs_host/nginx/dq-app--external-nginx.conf ]; then if [ ! -f /nginx_configs_host/nginx/dq-app--external-nginx.conf ]; then
cp /nginx_configs_host/nginx/nginx_dq.conf.example /nginx_configs_host/nginx/dq-app--external-nginx.conf; cp /nginx_configs_host/nginx/nginx_dq.conf.example /nginx_configs_host/nginx/dq-app--external-nginx.conf;
echo 'INIT: Created new nginx config with correct paths'; echo 'INIT: Created new nginx config with correct paths';
fi && fi &&
mkdir -p /app/public/media/errors && mkdir -p /app/public/media/errors &&
cp /app/dicquo/templates/static_404.html /app/public/media/errors/404.html && cp /home/app/web/dicquo/templates/static_404.html /app/public/media/errors/404.html &&
cp /app/dicquo/templates/static_500.html /app/public/media/errors/500.html && cp /home/app/web/dicquo/templates/static_500.html /app/public/media/errors/500.html &&
gunicorn --workers 3 --bind 0.0.0.0:8000 dicquo.wsgi:application" gunicorn --workers 3 --bind 0.0.0.0:8000 dicquo.wsgi:application"
# 4. Проброс портов (Внешний Nginx -> localhost:8010) # 4. Проброс портов (Внешний Nginx -> localhost:8010)
@@ -57,17 +59,24 @@ services:
volumes: volumes:
# База данных # База данных
# Монтируем папку database с хоста в папку с базой внутри контейнера. # Монтируем папку database с хоста в папку с базой внутри контейнера.
# Путь в контейнере: /app/database (так как Django ищет базу в BASE_DIR.parent/database) # Путь в контейнере: /home/app/web/database (так как Django ищет базу в BASE_DIR.parent/database)
- ./database:/app/database - ./database:/home/app/web/database
# Медиа (папка media должна быть рядом с docker-compose.yml) # Медиа (папка media должна быть рядом с docker-compose.yml)
- ./media:/app/public/media - ./media:/home/app/web/public/media
# Конфиги (Монтируем папку ./config с хоста в /nginx_configs_host внутри контейнера) # Конфиги (Монтируем папку ./config с хоста в /nginx_configs_host внутри контейнера)
# Это нужно, чтобы скрипт запуска мог положить туда .example конфиг и прочитать боевой конфиг. # Это нужно, чтобы скрипт запуска мог положить туда .example конфиг и прочитать боевой конфиг.
- ./config:/nginx_configs_host - ./config:/nginx_configs_host
# 6. Переменные окружения # 6. Пользователь и права
user: "1000:1000"
# Когда нужна отладка процессов внутри контейнера, можно временно раскомментировать эту строку и запустить контейнер с правами root.
# cap_add:
# - SYS_PTRACE
# 7. Переменные окружения
env_file: env_file:
- .env - .env
environment: environment:
@@ -76,14 +85,24 @@ services:
# Передаем переменную с путем на хосте внутрь контейнера, чтобы sed мог её использовать # Передаем переменную с путем на хосте внутрь контейнера, чтобы sed мог её использовать
- HOST_PROJECT_PATH=${HOST_PROJECT_PATH:-/home/default_user/projects/dq-site} - HOST_PROJECT_PATH=${HOST_PROJECT_PATH:-/home/default_user/projects/dq-site}
# 7. Логирование (Ротация) # 8. Проверка здоровья контейнера (Healthcheck)
# Docker будет периодически проверять статус контейнера. Это критично для Watchtower!
# Если контейнер объявлен "unhealthy", Watchtower сначала остановит старый образ, потом запустит новый.
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/').read()"]
interval: 30s # Проверка каждые 30 секунд
timeout: 3s # Таймаут ответа - 3 секунды
start_period: 10s # Даем 10 секунд на стартап перед первой проверкой
retries: 3 # Unhealthy после 3 неудачных попыток
# 9. Логирование (Ротация)
logging: logging:
driver: "json-file" driver: "json-file"
options: options:
max-size: "10m" max-size: "10m"
max-file: "3" max-file: "3"
# 8. Ресурсы # 10. Ресурсы
deploy: deploy:
resources: resources:
limits: limits:
@@ -106,10 +125,13 @@ services:
- REPO_PASS=${REPO_PASS} - REPO_PASS=${REPO_PASS}
- WATCHTOWER_SCOPE=dq-scope - WATCHTOWER_SCOPE=dq-scope
- WATCHTOWER_CLEANUP=true # Удалять старые образы после обновления - WATCHTOWER_CLEANUP=true # Удалять старые образы после обновления
- WATCHTOWER_POLL_INTERVAL=1800 # Проверять каждые 30 минут
- DOCKER_API_VERSION=1.44 - DOCKER_API_VERSION=1.44
# Дополнительные опции для правильной работы с healthcheck
- WATCHTOWER_WAIT_ON_TIMEOUT=60 # Ждем 60 сек пока контейнер станет healthy перед финализацией
- WATCHTOWER_LIFECYCLE_HOOKS=true # Включаем lifecycle hooks для graceful shutdown
command: --interval 1800 --cleanup # Проверять каждые 30 минут
logging: logging:
driver: "json-file" driver: "json-file"
options: options:
max-size: "10m" max-size: "10m"
max-file: "3" max-file: "3"

310
poetry.lock generated
View File

@@ -49,13 +49,13 @@ files = [
[[package]] [[package]]
name = "django" name = "django"
version = "6.0.2" version = "6.0.3"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
optional = false optional = false
python-versions = ">=3.12" python-versions = ">=3.12"
files = [ files = [
{file = "django-6.0.2-py3-none-any.whl", hash = "sha256:610dd3b13d15ec3f1e1d257caedd751db8033c5ad8ea0e2d1219a8acf446ecc6"}, {file = "django-6.0.3-py3-none-any.whl", hash = "sha256:2e5974441491ddb34c3f13d5e7a9f97b07ba03bf70234c0a9c68b79bbb235bc3"},
{file = "django-6.0.2.tar.gz", hash = "sha256:3046a53b0e40d4b676c3b774c73411d7184ae2745fe8ce5e45c0f33d3ddb71a7"}, {file = "django-6.0.3.tar.gz", hash = "sha256:90be765ee756af8a6cbd6693e56452404b5ad15294f4d5e40c0a55a0f4870fe1"},
] ]
[package.dependencies] [package.dependencies]
@@ -67,22 +67,51 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
argon2 = ["argon2-cffi (>=23.1.0)"] argon2 = ["argon2-cffi (>=23.1.0)"]
bcrypt = ["bcrypt (>=4.1.1)"] bcrypt = ["bcrypt (>=4.1.1)"]
[[package]]
name = "django-appconf"
version = "1.2.0"
description = "A helper class for handling configuration defaults of packaged apps gracefully."
optional = false
python-versions = ">=3.9"
files = [
{file = "django_appconf-1.2.0-py3-none-any.whl", hash = "sha256:b81bce5ef0ceb9d84df48dfb623a32235d941c78cc5e45dbb6947f154ea277f4"},
{file = "django_appconf-1.2.0.tar.gz", hash = "sha256:15a88d60dd942d6059f467412fe4581db632ef03018a3c183fb415d6fc9e5cec"},
]
[package.dependencies]
django = "*"
[[package]] [[package]]
name = "django-environ" name = "django-environ"
version = "0.12.1" version = "0.13.0"
description = "A package that allows you to utilize 12factor inspired environment variables to configure your Django application." description = "A package that allows you to utilize 12factor inspired environment variables to configure your Django application."
optional = false optional = false
python-versions = "<4,>=3.9" python-versions = "<4,>=3.9"
files = [ files = [
{file = "django_environ-0.12.1-py2.py3-none-any.whl", hash = "sha256:064ba2d5082f833e6d7fe4def4928bde1eedc0248a417575da7db147aeec1c20"}, {file = "django_environ-0.13.0-py3-none-any.whl", hash = "sha256:37799d14cd78222c6fd8298e48bfe17965ff8e586091ad66a463e52e0e7b799e"},
{file = "django_environ-0.12.1.tar.gz", hash = "sha256:22859c6e905ab7637fa3348d1787543bb4492f38d761104a3ce0519b7b752845"}, {file = "django_environ-0.13.0.tar.gz", hash = "sha256:6c401e4c219442c2c4588c2116d5292b5484a6f69163ed09cd41f3943bfb645f"},
] ]
[package.extras] [package.extras]
develop = ["coverage[toml] (>=5.0a4)", "furo (>=2024.8.6)", "pytest (>=4.6.11)", "setuptools (>=71.0.0)", "sphinx (>=5.0)", "sphinx-notfound-page"] develop = ["coverage[toml] (>=5.0a4)", "furo (>=2024.8.6)", "pytest (>=4.6.11)", "setuptools (>=71.0.0)", "sphinx (>=5.0)", "sphinx-copybutton", "sphinx-notfound-page"]
docs = ["furo (>=2024.8.6)", "sphinx (>=5.0)", "sphinx-notfound-page"] docs = ["furo (>=2024.8.6)", "sphinx (>=5.0)", "sphinx-copybutton", "sphinx-notfound-page"]
testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)", "setuptools (>=71.0.0)"] testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)", "setuptools (>=71.0.0)"]
[[package]]
name = "django-select2"
version = "8.4.8"
description = "This is a Django_ integration of Select2_."
optional = false
python-versions = ">=3.10"
files = [
{file = "django_select2-8.4.8-py3-none-any.whl", hash = "sha256:a2ce6a4c556dd2d4d57eb3753618d6f31f8d3910e9d9fa1b686d9340f50b14eb"},
{file = "django_select2-8.4.8.tar.gz", hash = "sha256:592e52effff2b5850cb7c98b265715b6704fb784699c4aedddfdd8ae1ffa1e81"},
]
[package.dependencies]
django = ">=4.2"
django-appconf = ">=0.6.0"
[[package]] [[package]]
name = "django-taggit" name = "django-taggit"
version = "6.1.0" version = "6.1.0"
@@ -99,13 +128,13 @@ Django = ">=4.1"
[[package]] [[package]]
name = "etpgrf" name = "etpgrf"
version = "0.1.4" version = "0.1.6.post1"
description = "Electro-Typographer: Python library for advanced web typography (non-breaking spaces, hyphenation, hanging punctuation and ." description = "Electro-Typographer: Python library for advanced web typography (non-breaking spaces, hyphenation, hanging punctuation and ."
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
files = [ files = [
{file = "etpgrf-0.1.4-py3-none-any.whl", hash = "sha256:62d4371e1b5fab06b99f79bd351767aed8baf7d041cae7e5d4eb63f7c9545114"}, {file = "etpgrf-0.1.6.post1-py3-none-any.whl", hash = "sha256:0863b14385bdacdd405f137ca2ce6bdb6f683f0189e8c927196a1eee754366be"},
{file = "etpgrf-0.1.4.tar.gz", hash = "sha256:c699382c292e3110915331dd5539e7dde0c961e4f4ca65cf8db0e01e84dab72f"}, {file = "etpgrf-0.1.6.post1.tar.gz", hash = "sha256:984d201cff232a58c05b6f4455a50f822162520df829ad4d543bfe0b7fd962a9"},
] ]
[package.dependencies] [package.dependencies]
@@ -422,142 +451,125 @@ files = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "2026.1.15" version = "2026.2.28"
description = "Alternative regular expression module, to replace re." description = "Alternative regular expression module, to replace re."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.10"
files = [ files = [
{file = "regex-2026.1.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e3dd93c8f9abe8aa4b6c652016da9a3afa190df5ad822907efe6b206c09896e"}, {file = "regex-2026.2.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fc48c500838be6882b32748f60a15229d2dea96e59ef341eaa96ec83538f498d"},
{file = "regex-2026.1.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97499ff7862e868b1977107873dd1a06e151467129159a6ffd07b66706ba3a9f"}, {file = "regex-2026.2.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2afa673660928d0b63d84353c6c08a8a476ddfc4a47e11742949d182e6863ce8"},
{file = "regex-2026.1.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bda75ebcac38d884240914c6c43d8ab5fb82e74cde6da94b43b17c411aa4c2b"}, {file = "regex-2026.2.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7ab218076eb0944549e7fe74cf0e2b83a82edb27e81cc87411f76240865e04d5"},
{file = "regex-2026.1.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7dcc02368585334f5bc81fc73a2a6a0bbade60e7d83da21cead622faf408f32c"}, {file = "regex-2026.2.28-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d63db12e45a9b9f064bfe4800cefefc7e5f182052e4c1b774d46a40ab1d9bb"},
{file = "regex-2026.1.15-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:693b465171707bbe882a7a05de5e866f33c76aa449750bee94a8d90463533cc9"}, {file = "regex-2026.2.28-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:195237dc327858a7721bf8b0bbbef797554bc13563c3591e91cd0767bacbe359"},
{file = "regex-2026.1.15-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b0d190e6f013ea938623a58706d1469a62103fb2a241ce2873a9906e0386582c"}, {file = "regex-2026.2.28-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b387a0d092dac157fb026d737dde35ff3e49ef27f285343e7c6401851239df27"},
{file = "regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ff818702440a5878a81886f127b80127f5d50563753a28211482867f8318106"}, {file = "regex-2026.2.28-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3935174fa4d9f70525a4367aaff3cb8bc0548129d114260c29d9dfa4a5b41692"},
{file = "regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f052d1be37ef35a54e394de66136e30fa1191fab64f71fc06ac7bc98c9a84618"}, {file = "regex-2026.2.28-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b2b23587b26496ff5fd40df4278becdf386813ec00dc3533fa43a4cf0e2ad3c"},
{file = "regex-2026.1.15-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6bfc31a37fd1592f0c4fc4bfc674b5c42e52efe45b4b7a6a14f334cca4bcebe4"}, {file = "regex-2026.2.28-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3b24bd7e9d85dc7c6a8bd2aa14ecd234274a0248335a02adeb25448aecdd420d"},
{file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d6ce5ae80066b319ae3bc62fd55a557c9491baa5efd0d355f0de08c4ba54e79"}, {file = "regex-2026.2.28-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd477d5f79920338107f04aa645f094032d9e3030cc55be581df3d1ef61aa318"},
{file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1704d204bd42b6bb80167df0e4554f35c255b579ba99616def38f69e14a5ccb9"}, {file = "regex-2026.2.28-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b49eb78048c6354f49e91e4b77da21257fecb92256b6d599ae44403cab30b05b"},
{file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e3174a5ed4171570dc8318afada56373aa9289eb6dc0d96cceb48e7358b0e220"}, {file = "regex-2026.2.28-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a25c7701e4f7a70021db9aaf4a4a0a67033c6318752146e03d1b94d32006217e"},
{file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:87adf5bd6d72e3e17c9cb59ac4096b1faaf84b7eb3037a5ffa61c4b4370f0f13"}, {file = "regex-2026.2.28-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9dd450db6458387167e033cfa80887a34c99c81d26da1bf8b0b41bf8c9cac88e"},
{file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e85dc94595f4d766bd7d872a9de5ede1ca8d3063f3bdf1e2c725f5eb411159e3"}, {file = "regex-2026.2.28-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2954379dd20752e82d22accf3ff465311cbb2bac6c1f92c4afd400e1757f7451"},
{file = "regex-2026.1.15-cp310-cp310-win32.whl", hash = "sha256:21ca32c28c30d5d65fc9886ff576fc9b59bbca08933e844fa2363e530f4c8218"}, {file = "regex-2026.2.28-cp310-cp310-win32.whl", hash = "sha256:1f8b17be5c27a684ea6759983c13506bd77bfc7c0347dff41b18ce5ddd2ee09a"},
{file = "regex-2026.1.15-cp310-cp310-win_amd64.whl", hash = "sha256:3038a62fc7d6e5547b8915a3d927a0fbeef84cdbe0b1deb8c99bbd4a8961b52a"}, {file = "regex-2026.2.28-cp310-cp310-win_amd64.whl", hash = "sha256:dd8847c4978bc3c7e6c826fb745f5570e518b8459ac2892151ce6627c7bc00d5"},
{file = "regex-2026.1.15-cp310-cp310-win_arm64.whl", hash = "sha256:505831646c945e3e63552cc1b1b9b514f0e93232972a2d5bedbcc32f15bc82e3"}, {file = "regex-2026.2.28-cp310-cp310-win_arm64.whl", hash = "sha256:73cdcdbba8028167ea81490c7f45280113e41db2c7afb65a276f4711fa3bcbff"},
{file = "regex-2026.1.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ae6020fb311f68d753b7efa9d4b9a5d47a5d6466ea0d5e3b5a471a960ea6e4a"}, {file = "regex-2026.2.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e621fb7c8dc147419b28e1702f58a0177ff8308a76fa295c71f3e7827849f5d9"},
{file = "regex-2026.1.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eddf73f41225942c1f994914742afa53dc0d01a6e20fe14b878a1b1edc74151f"}, {file = "regex-2026.2.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d5bef2031cbf38757a0b0bc4298bb4824b6332d28edc16b39247228fbdbad97"},
{file = "regex-2026.1.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e8cd52557603f5c66a548f69421310886b28b7066853089e1a71ee710e1cdc1"}, {file = "regex-2026.2.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bcb399ed84eabf4282587ba151f2732ad8168e66f1d3f85b1d038868fe547703"},
{file = "regex-2026.1.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5170907244b14303edc5978f522f16c974f32d3aa92109fabc2af52411c9433b"}, {file = "regex-2026.2.28-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c1b34dfa72f826f535b20712afa9bb3ba580020e834f3c69866c5bddbf10098"},
{file = "regex-2026.1.15-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2748c1ec0663580b4510bd89941a31560b4b439a0b428b49472a3d9944d11cd8"}, {file = "regex-2026.2.28-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:851fa70df44325e1e4cdb79c5e676e91a78147b1b543db2aec8734d2add30ec2"},
{file = "regex-2026.1.15-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f2775843ca49360508d080eaa87f94fa248e2c946bbcd963bb3aae14f333413"}, {file = "regex-2026.2.28-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:516604edd17b1c2c3e579cf4e9b25a53bf8fa6e7cedddf1127804d3e0140ca64"},
{file = "regex-2026.1.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9ea2604370efc9a174c1b5dcc81784fb040044232150f7f33756049edfc9026"}, {file = "regex-2026.2.28-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7ce83654d1ab701cb619285a18a8e5a889c1216d746ddc710c914ca5fd71022"},
{file = "regex-2026.1.15-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcd31594264029b57bf16f37fd7248a70b3b764ed9e0839a8f271b2d22c0785"}, {file = "regex-2026.2.28-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2791948f7c70bb9335a9102df45e93d428f4b8128020d85920223925d73b9e1"},
{file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c08c1f3e34338256732bd6938747daa3c0d5b251e04b6e43b5813e94d503076e"}, {file = "regex-2026.2.28-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03a83cc26aa2acda6b8b9dfe748cf9e84cbd390c424a1de34fdcef58961a297a"},
{file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e43a55f378df1e7a4fa3547c88d9a5a9b7113f653a66821bcea4718fe6c58763"}, {file = "regex-2026.2.28-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ec6f5674c5dc836994f50f1186dd1fafde4be0666aae201ae2fcc3d29d8adf27"},
{file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f82110ab962a541737bd0ce87978d4c658f06e7591ba899192e2712a517badbb"}, {file = "regex-2026.2.28-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:50c2fc924749543e0eacc93ada6aeeb3ea5f6715825624baa0dccaec771668ae"},
{file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:27618391db7bdaf87ac6c92b31e8f0dfb83a9de0075855152b720140bda177a2"}, {file = "regex-2026.2.28-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ba55c50f408fb5c346a3a02d2ce0ebc839784e24f7c9684fde328ff063c3cdea"},
{file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bfb0d6be01fbae8d6655c8ca21b3b72458606c4aec9bbc932db758d47aba6db1"}, {file = "regex-2026.2.28-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:edb1b1b3a5576c56f08ac46f108c40333f222ebfd5cf63afdfa3aab0791ebe5b"},
{file = "regex-2026.1.15-cp311-cp311-win32.whl", hash = "sha256:b10e42a6de0e32559a92f2f8dc908478cc0fa02838d7dbe764c44dca3fa13569"}, {file = "regex-2026.2.28-cp311-cp311-win32.whl", hash = "sha256:948c12ef30ecedb128903c2c2678b339746eb7c689c5c21957c4a23950c96d15"},
{file = "regex-2026.1.15-cp311-cp311-win_amd64.whl", hash = "sha256:e9bf3f0bbdb56633c07d7116ae60a576f846efdd86a8848f8d62b749e1209ca7"}, {file = "regex-2026.2.28-cp311-cp311-win_amd64.whl", hash = "sha256:fd63453f10d29097cc3dc62d070746523973fb5aa1c66d25f8558bebd47fed61"},
{file = "regex-2026.1.15-cp311-cp311-win_arm64.whl", hash = "sha256:41aef6f953283291c4e4e6850607bd71502be67779586a61472beacb315c97ec"}, {file = "regex-2026.2.28-cp311-cp311-win_arm64.whl", hash = "sha256:00f2b8d9615aa165fdff0a13f1a92049bfad555ee91e20d246a51aa0b556c60a"},
{file = "regex-2026.1.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c8fcc5793dde01641a35905d6731ee1548f02b956815f8f1cab89e515a5bdf1"}, {file = "regex-2026.2.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fcf26c3c6d0da98fada8ae4ef0aa1c3405a431c0a77eb17306d38a89b02adcd7"},
{file = "regex-2026.1.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bfd876041a956e6a90ad7cdb3f6a630c07d491280bfeed4544053cd434901681"}, {file = "regex-2026.2.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02473c954af35dd2defeb07e44182f5705b30ea3f351a7cbffa9177beb14da5d"},
{file = "regex-2026.1.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9250d087bc92b7d4899ccd5539a1b2334e44eee85d848c4c1aef8e221d3f8c8f"}, {file = "regex-2026.2.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b65d33a17101569f86d9c5966a8b1d7fbf8afdda5a8aa219301b0a80f58cf7d"},
{file = "regex-2026.1.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8a154cf6537ebbc110e24dabe53095e714245c272da9c1be05734bdad4a61aa"}, {file = "regex-2026.2.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71dcecaa113eebcc96622c17692672c2d104b1d71ddf7adeda90da7ddeb26fc"},
{file = "regex-2026.1.15-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8050ba2e3ea1d8731a549e83c18d2f0999fbc99a5f6bd06b4c91449f55291804"}, {file = "regex-2026.2.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:481df4623fa4969c8b11f3433ed7d5e3dc9cec0f008356c3212b3933fb77e3d8"},
{file = "regex-2026.1.15-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf065240704cb8951cc04972cf107063917022511273e0969bdb34fc173456c"}, {file = "regex-2026.2.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64e7c6ad614573e0640f271e811a408d79a9e1fe62a46adb602f598df42a818d"},
{file = "regex-2026.1.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c32bef3e7aeee75746748643667668ef941d28b003bfc89994ecf09a10f7a1b5"}, {file = "regex-2026.2.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b08a06976ff4fb0d83077022fde3eca06c55432bb997d8c0495b9a4e9872f4"},
{file = "regex-2026.1.15-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5eaa4a4c5b1906bd0d2508d68927f15b81821f85092e06f1a34a4254b0e1af3"}, {file = "regex-2026.2.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:864cdd1a2ef5716b0ab468af40139e62ede1b3a53386b375ec0786bb6783fc05"},
{file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:86c1077a3cc60d453d4084d5b9649065f3bf1184e22992bd322e1f081d3117fb"}, {file = "regex-2026.2.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:511f7419f7afab475fd4d639d4aedfc54205bcb0800066753ef68a59f0f330b5"},
{file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2b091aefc05c78d286657cd4db95f2e6313375ff65dcf085e42e4c04d9c8d410"}, {file = "regex-2026.2.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b42f7466e32bf15a961cf09f35fa6323cc72e64d3d2c990b10de1274a5da0a59"},
{file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:57e7d17f59f9ebfa9667e6e5a1c0127b96b87cb9cede8335482451ed00788ba4"}, {file = "regex-2026.2.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8710d61737b0c0ce6836b1da7109f20d495e49b3809f30e27e9560be67a257bf"},
{file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6c4dcdfff2c08509faa15d36ba7e5ef5fcfab25f1e8f85a0c8f45bc3a30725d"}, {file = "regex-2026.2.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4390c365fd2d45278f45afd4673cb90f7285f5701607e3ad4274df08e36140ae"},
{file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf8ff04c642716a7f2048713ddc6278c5fd41faa3b9cab12607c7abecd012c22"}, {file = "regex-2026.2.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb3b1db8ff6c7b8bf838ab05583ea15230cb2f678e569ab0e3a24d1e8320940b"},
{file = "regex-2026.1.15-cp312-cp312-win32.whl", hash = "sha256:82345326b1d8d56afbe41d881fdf62f1926d7264b2fc1537f99ae5da9aad7913"}, {file = "regex-2026.2.28-cp312-cp312-win32.whl", hash = "sha256:f8ed9a5d4612df9d4de15878f0bc6aa7a268afbe5af21a3fdd97fa19516e978c"},
{file = "regex-2026.1.15-cp312-cp312-win_amd64.whl", hash = "sha256:4def140aa6156bc64ee9912383d4038f3fdd18fee03a6f222abd4de6357ce42a"}, {file = "regex-2026.2.28-cp312-cp312-win_amd64.whl", hash = "sha256:01d65fd24206c8e1e97e2e31b286c59009636c022eb5d003f52760b0f42155d4"},
{file = "regex-2026.1.15-cp312-cp312-win_arm64.whl", hash = "sha256:c6c565d9a6e1a8d783c1948937ffc377dd5771e83bd56de8317c450a954d2056"}, {file = "regex-2026.2.28-cp312-cp312-win_arm64.whl", hash = "sha256:c0b5ccbb8ffb433939d248707d4a8b31993cb76ab1a0187ca886bf50e96df952"},
{file = "regex-2026.1.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e69d0deeb977ffe7ed3d2e4439360089f9c3f217ada608f0f88ebd67afb6385e"}, {file = "regex-2026.2.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6d63a07e5ec8ce7184452cb00c41c37b49e67dc4f73b2955b5b8e782ea970784"},
{file = "regex-2026.1.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3601ffb5375de85a16f407854d11cca8fe3f5febbe3ac78fb2866bb220c74d10"}, {file = "regex-2026.2.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e59bc8f30414d283ae8ee1617b13d8112e7135cb92830f0ec3688cb29152585a"},
{file = "regex-2026.1.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4c5ef43b5c2d4114eb8ea424bb8c9cec01d5d17f242af88b2448f5ee81caadbc"}, {file = "regex-2026.2.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:de0cf053139f96219ccfabb4a8dd2d217c8c82cb206c91d9f109f3f552d6b43d"},
{file = "regex-2026.1.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:968c14d4f03e10b2fd960f1d5168c1f0ac969381d3c1fcc973bc45fb06346599"}, {file = "regex-2026.2.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb4db2f17e6484904f986c5a657cec85574c76b5c5e61c7aae9ffa1bc6224f95"},
{file = "regex-2026.1.15-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56a5595d0f892f214609c9f76b41b7428bed439d98dc961efafdd1354d42baae"}, {file = "regex-2026.2.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52b017b35ac2214d0db5f4f90e303634dc44e4aba4bd6235a27f97ecbe5b0472"},
{file = "regex-2026.1.15-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf650f26087363434c4e560011f8e4e738f6f3e029b85d4904c50135b86cfa5"}, {file = "regex-2026.2.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:69fc560ccbf08a09dc9b52ab69cacfae51e0ed80dc5693078bdc97db2f91ae96"},
{file = "regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18388a62989c72ac24de75f1449d0fb0b04dfccd0a1a7c1c43af5eb503d890f6"}, {file = "regex-2026.2.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e61eea47230eba62a31f3e8a0e3164d0f37ef9f40529fb2c79361bc6b53d2a92"},
{file = "regex-2026.1.15-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d220a2517f5893f55daac983bfa9fe998a7dbcaee4f5d27a88500f8b7873788"}, {file = "regex-2026.2.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f5c0b182ad4269e7381b7c27fdb0408399881f7a92a4624fd5487f2971dfc11"},
{file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9c08c2fbc6120e70abff5d7f28ffb4d969e14294fb2143b4b5c7d20e46d1714"}, {file = "regex-2026.2.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:96f6269a2882fbb0ee76967116b83679dc628e68eaea44e90884b8d53d833881"},
{file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ef7d5d4bd49ec7364315167a4134a015f61e8266c6d446fc116a9ac4456e10d"}, {file = "regex-2026.2.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b5acd4b6a95f37c3c3828e5d053a7d4edaedb85de551db0153754924cb7c83e3"},
{file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e42844ad64194fa08d5ccb75fe6a459b9b08e6d7296bd704460168d58a388f3"}, {file = "regex-2026.2.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2234059cfe33d9813a3677ef7667999caea9eeaa83fef98eb6ce15c6cf9e0215"},
{file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cfecdaa4b19f9ca534746eb3b55a5195d5c95b88cac32a205e981ec0a22b7d31"}, {file = "regex-2026.2.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c15af43c72a7fb0c97cbc66fa36a43546eddc5c06a662b64a0cbf30d6ac40944"},
{file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08df9722d9b87834a3d701f3fca570b2be115654dbfd30179f30ab2f39d606d3"}, {file = "regex-2026.2.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9185cc63359862a6e80fe97f696e04b0ad9a11c4ac0a4a927f979f611bfe3768"},
{file = "regex-2026.1.15-cp313-cp313-win32.whl", hash = "sha256:d426616dae0967ca225ab12c22274eb816558f2f99ccb4a1d52ca92e8baf180f"}, {file = "regex-2026.2.28-cp313-cp313-win32.whl", hash = "sha256:fb66e5245db9652abd7196ace599b04d9c0e4aa7c8f0e2803938377835780081"},
{file = "regex-2026.1.15-cp313-cp313-win_amd64.whl", hash = "sha256:febd38857b09867d3ed3f4f1af7d241c5c50362e25ef43034995b77a50df494e"}, {file = "regex-2026.2.28-cp313-cp313-win_amd64.whl", hash = "sha256:71a911098be38c859ceb3f9a9ce43f4ed9f4c6720ad8684a066ea246b76ad9ff"},
{file = "regex-2026.1.15-cp313-cp313-win_arm64.whl", hash = "sha256:8e32f7896f83774f91499d239e24cebfadbc07639c1494bb7213983842348337"}, {file = "regex-2026.2.28-cp313-cp313-win_arm64.whl", hash = "sha256:39bb5727650b9a0275c6a6690f9bb3fe693a7e6cc5c3155b1240aedf8926423e"},
{file = "regex-2026.1.15-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ec94c04149b6a7b8120f9f44565722c7ae31b7a6d2275569d2eefa76b83da3be"}, {file = "regex-2026.2.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:97054c55db06ab020342cc0d35d6f62a465fa7662871190175f1ad6c655c028f"},
{file = "regex-2026.1.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40c86d8046915bb9aeb15d3f3f15b6fd500b8ea4485b30e1bbc799dab3fe29f8"}, {file = "regex-2026.2.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d25a10811de831c2baa6aef3c0be91622f44dd8d31dd12e69f6398efb15e48b"},
{file = "regex-2026.1.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:726ea4e727aba21643205edad8f2187ec682d3305d790f73b7a51c7587b64bdd"}, {file = "regex-2026.2.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d6cfe798d8da41bb1862ed6e0cba14003d387c3c0c4a5d45591076ae9f0ce2f8"},
{file = "regex-2026.1.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cb740d044aff31898804e7bf1181cc72c03d11dfd19932b9911ffc19a79070a"}, {file = "regex-2026.2.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd0ce43e71d825b7c0661f9c54d4d74bd97c56c3fd102a8985bcfea48236bacb"},
{file = "regex-2026.1.15-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05d75a668e9ea16f832390d22131fe1e8acc8389a694c8febc3e340b0f810b93"}, {file = "regex-2026.2.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00945d007fd74a9084d2ab79b695b595c6b7ba3698972fadd43e23230c6979c1"},
{file = "regex-2026.1.15-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d991483606f3dbec93287b9f35596f41aa2e92b7c2ebbb935b63f409e243c9af"}, {file = "regex-2026.2.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bec23c11cbbf09a4df32fe50d57cbdd777bc442269b6e39a1775654f1c95dee2"},
{file = "regex-2026.1.15-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:194312a14819d3e44628a44ed6fea6898fdbecb0550089d84c403475138d0a09"}, {file = "regex-2026.2.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5cdcc17d935c8f9d3f4db5c2ebe2640c332e3822ad5d23c2f8e0228e6947943a"},
{file = "regex-2026.1.15-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe2fda4110a3d0bc163c2e0664be44657431440722c5c5315c65155cab92f9e5"}, {file = "regex-2026.2.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a448af01e3d8031c89c5d902040b124a5e921a25c4e5e07a861ca591ce429341"},
{file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:124dc36c85d34ef2d9164da41a53c1c8c122cfb1f6e1ec377a1f27ee81deb794"}, {file = "regex-2026.2.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:10d28e19bd4888e4abf43bd3925f3c134c52fdf7259219003588a42e24c2aa25"},
{file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1774cd1981cd212506a23a14dba7fdeaee259f5deba2df6229966d9911e767a"}, {file = "regex-2026.2.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:99985a2c277dcb9ccb63f937451af5d65177af1efdeb8173ac55b61095a0a05c"},
{file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b5f7d8d2867152cdb625e72a530d2ccb48a3d199159144cbdd63870882fb6f80"}, {file = "regex-2026.2.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e1e7b24cb3ae9953a560c563045d1ba56ee4749fbd05cf21ba571069bd7be81b"},
{file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:492534a0ab925d1db998defc3c302dae3616a2fc3fe2e08db1472348f096ddf2"}, {file = "regex-2026.2.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d8511a01d0e4ee1992eb3ba19e09bc1866fe03f05129c3aec3fdc4cbc77aad3f"},
{file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c661fc820cfb33e166bf2450d3dadbda47c8d8981898adb9b6fe24e5e582ba60"}, {file = "regex-2026.2.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aaffaecffcd2479ce87aa1e74076c221700b7c804e48e98e62500ee748f0f550"},
{file = "regex-2026.1.15-cp313-cp313t-win32.whl", hash = "sha256:99ad739c3686085e614bf77a508e26954ff1b8f14da0e3765ff7abbf7799f952"}, {file = "regex-2026.2.28-cp313-cp313t-win32.whl", hash = "sha256:ef77bdde9c9eba3f7fa5b58084b29bbcc74bcf55fdbeaa67c102a35b5bd7e7cc"},
{file = "regex-2026.1.15-cp313-cp313t-win_amd64.whl", hash = "sha256:32655d17905e7ff8ba5c764c43cb124e34a9245e45b83c22e81041e1071aee10"}, {file = "regex-2026.2.28-cp313-cp313t-win_amd64.whl", hash = "sha256:98adf340100cbe6fbaf8e6dc75e28f2c191b1be50ffefe292fb0e6f6eefdb0d8"},
{file = "regex-2026.1.15-cp313-cp313t-win_arm64.whl", hash = "sha256:b2a13dd6a95e95a489ca242319d18fc02e07ceb28fa9ad146385194d95b3c829"}, {file = "regex-2026.2.28-cp313-cp313t-win_arm64.whl", hash = "sha256:2fb950ac1d88e6b6a9414381f403797b236f9fa17e1eee07683af72b1634207b"},
{file = "regex-2026.1.15-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d920392a6b1f353f4aa54328c867fec3320fa50657e25f64abf17af054fc97ac"}, {file = "regex-2026.2.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:78454178c7df31372ea737996fb7f36b3c2c92cccc641d251e072478afb4babc"},
{file = "regex-2026.1.15-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b5a28980a926fa810dbbed059547b02783952e2efd9c636412345232ddb87ff6"}, {file = "regex-2026.2.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:5d10303dd18cedfd4d095543998404df656088240bcfd3cd20a8f95b861f74bd"},
{file = "regex-2026.1.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:621f73a07595d83f28952d7bd1e91e9d1ed7625fb7af0064d3516674ec93a2a2"}, {file = "regex-2026.2.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:19a9c9e0a8f24f39d575a6a854d516b48ffe4cbdcb9de55cb0570a032556ecff"},
{file = "regex-2026.1.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d7d92495f47567a9b1669c51fc8d6d809821849063d168121ef801bbc213846"}, {file = "regex-2026.2.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09500be324f49b470d907b3ef8af9afe857f5cca486f853853f7945ddbf75911"},
{file = "regex-2026.1.15-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dd16fba2758db7a3780a051f245539c4451ca20910f5a5e6ea1c08d06d4a76b"}, {file = "regex-2026.2.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb1c4ff62277d87a7335f2c1ea4e0387b8f2b3ad88a64efd9943906aafad4f33"},
{file = "regex-2026.1.15-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1e1808471fbe44c1a63e5f577a1d5f02fe5d66031dcbdf12f093ffc1305a858e"}, {file = "regex-2026.2.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b8b3f1be1738feadc69f62daa250c933e85c6f34fa378f54a7ff43807c1b9117"},
{file = "regex-2026.1.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0751a26ad39d4f2ade8fe16c59b2bf5cb19eb3d2cd543e709e583d559bd9efde"}, {file = "regex-2026.2.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc8ed8c3f41c27acb83f7b6a9eb727a73fc6663441890c5cb3426a5f6a91ce7d"},
{file = "regex-2026.1.15-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0c7684c7f9ca241344ff95a1de964f257a5251968484270e91c25a755532c5"}, {file = "regex-2026.2.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa539be029844c0ce1114762d2952ab6cfdd7c7c9bd72e0db26b94c3c36dcc5a"},
{file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74f45d170a21df41508cb67165456538425185baaf686281fa210d7e729abc34"}, {file = "regex-2026.2.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7900157786428a79615a8264dac1f12c9b02957c473c8110c6b1f972dcecaddf"},
{file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f1862739a1ffb50615c0fde6bae6569b5efbe08d98e59ce009f68a336f64da75"}, {file = "regex-2026.2.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0b1d2b07614d95fa2bf8a63fd1e98bd8fa2b4848dc91b1efbc8ba219fdd73952"},
{file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:453078802f1b9e2b7303fb79222c054cb18e76f7bdc220f7530fdc85d319f99e"}, {file = "regex-2026.2.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b389c61aa28a79c2e0527ac36da579869c2e235a5b208a12c5b5318cda2501d8"},
{file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:a30a68e89e5a218b8b23a52292924c1f4b245cb0c68d1cce9aec9bbda6e2c160"}, {file = "regex-2026.2.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f467cb602f03fbd1ab1908f68b53c649ce393fde056628dc8c7e634dab6bfc07"},
{file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9479cae874c81bf610d72b85bb681a94c95722c127b55445285fb0e2c82db8e1"}, {file = "regex-2026.2.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c8cb2deba42f5ec1ede46374e990f8adc5e6456a57ac1a261b19be6f28e4e6"},
{file = "regex-2026.1.15-cp314-cp314-win32.whl", hash = "sha256:d639a750223132afbfb8f429c60d9d318aeba03281a5f1ab49f877456448dcf1"}, {file = "regex-2026.2.28-cp314-cp314-win32.whl", hash = "sha256:9036b400b20e4858d56d117108d7813ed07bb7803e3eed766675862131135ca6"},
{file = "regex-2026.1.15-cp314-cp314-win_amd64.whl", hash = "sha256:4161d87f85fa831e31469bfd82c186923070fc970b9de75339b68f0c75b51903"}, {file = "regex-2026.2.28-cp314-cp314-win_amd64.whl", hash = "sha256:1d367257cd86c1cbb97ea94e77b373a0bbc2224976e247f173d19e8f18b4afa7"},
{file = "regex-2026.1.15-cp314-cp314-win_arm64.whl", hash = "sha256:91c5036ebb62663a6b3999bdd2e559fd8456d17e2b485bf509784cd31a8b1705"}, {file = "regex-2026.2.28-cp314-cp314-win_arm64.whl", hash = "sha256:5e68192bb3a1d6fb2836da24aa494e413ea65853a21505e142e5b1064a595f3d"},
{file = "regex-2026.1.15-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ee6854c9000a10938c79238de2379bea30c82e4925a371711af45387df35cab8"}, {file = "regex-2026.2.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a5dac14d0872eeb35260a8e30bac07ddf22adc1e3a0635b52b02e180d17c9c7e"},
{file = "regex-2026.1.15-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c2b80399a422348ce5de4fe40c418d6299a0fa2803dd61dc0b1a2f28e280fcf"}, {file = "regex-2026.2.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ec0c608b7a7465ffadb344ed7c987ff2f11ee03f6a130b569aa74d8a70e8333c"},
{file = "regex-2026.1.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dca3582bca82596609959ac39e12b7dad98385b4fefccb1151b937383cec547d"}, {file = "regex-2026.2.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7815afb0ca45456613fdaf60ea9c993715511c8d53a83bc468305cbc0ee23c7"},
{file = "regex-2026.1.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71d476caa6692eea743ae5ea23cde3260677f70122c4d258ca952e5c2d4e84"}, {file = "regex-2026.2.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b059e71ec363968671693a78c5053bd9cb2fe410f9b8e4657e88377ebd603a2e"},
{file = "regex-2026.1.15-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c243da3436354f4af6c3058a3f81a97d47ea52c9bd874b52fd30274853a1d5df"}, {file = "regex-2026.2.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8cf76f1a29f0e99dcfd7aef1551a9827588aae5a737fe31442021165f1920dc"},
{file = "regex-2026.1.15-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8355ad842a7c7e9e5e55653eade3b7d1885ba86f124dd8ab1f722f9be6627434"}, {file = "regex-2026.2.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:180e08a435a0319e6a4821c3468da18dc7001987e1c17ae1335488dfe7518dd8"},
{file = "regex-2026.1.15-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f192a831d9575271a22d804ff1a5355355723f94f31d9eef25f0d45a152fdc1a"}, {file = "regex-2026.2.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e496956106fd59ba6322a8ea17141a27c5040e5ee8f9433ae92d4e5204462a0"},
{file = "regex-2026.1.15-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:166551807ec20d47ceaeec380081f843e88c8949780cd42c40f18d16168bed10"}, {file = "regex-2026.2.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bba2b18d70eeb7b79950f12f633beeecd923f7c9ad6f6bae28e59b4cb3ab046b"},
{file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9ca1cbdc0fbfe5e6e6f8221ef2309988db5bcede52443aeaee9a4ad555e0dac"}, {file = "regex-2026.2.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6db7bfae0f8a2793ff1f7021468ea55e2699d0790eb58ee6ab36ae43aa00bc5b"},
{file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b30bcbd1e1221783c721483953d9e4f3ab9c5d165aa709693d3f3946747b1aea"}, {file = "regex-2026.2.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d0b02e8b7e5874b48ae0f077ecca61c1a6a9f9895e9c6dfb191b55b242862033"},
{file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2a8d7b50c34578d0d3bf7ad58cde9652b7d683691876f83aedc002862a35dc5e"}, {file = "regex-2026.2.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:25b6eb660c5cf4b8c3407a1ed462abba26a926cc9965e164268a3267bcc06a43"},
{file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9d787e3310c6a6425eb346be4ff2ccf6eece63017916fd77fe8328c57be83521"}, {file = "regex-2026.2.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:5a932ea8ad5d0430351ff9c76c8db34db0d9f53c1d78f06022a21f4e290c5c18"},
{file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:619843841e220adca114118533a574a9cd183ed8a28b85627d2844c500a2b0db"}, {file = "regex-2026.2.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2c95e1a2b0f89d01e821ff4de1be4b5d73d1f4b0bf679fa27c1ad8d2327f1a"},
{file = "regex-2026.1.15-cp314-cp314t-win32.whl", hash = "sha256:e90b8db97f6f2c97eb045b51a6b2c5ed69cedd8392459e0642d4199b94fabd7e"}, {file = "regex-2026.2.28-cp314-cp314t-win32.whl", hash = "sha256:bbb882061f742eb5d46f2f1bd5304055be0a66b783576de3d7eef1bed4778a6e"},
{file = "regex-2026.1.15-cp314-cp314t-win_amd64.whl", hash = "sha256:5ef19071f4ac9f0834793af85bd04a920b4407715624e40cb7a0631a11137cdf"}, {file = "regex-2026.2.28-cp314-cp314t-win_amd64.whl", hash = "sha256:6591f281cb44dc13de9585b552cec6fc6cf47fb2fe7a48892295ee9bc4a612f9"},
{file = "regex-2026.1.15-cp314-cp314t-win_arm64.whl", hash = "sha256:ca89c5e596fc05b015f27561b3793dc2fa0917ea0d7507eebb448efd35274a70"}, {file = "regex-2026.2.28-cp314-cp314t-win_arm64.whl", hash = "sha256:dee50f1be42222f89767b64b283283ef963189da0dda4a515aa54a5563c62dec"},
{file = "regex-2026.1.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:55b4ea996a8e4458dd7b584a2f89863b1655dd3d17b88b46cbb9becc495a0ec5"}, {file = "regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2"},
{file = "regex-2026.1.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e1e28be779884189cdd57735e997f282b64fd7ccf6e2eef3e16e57d7a34a815"},
{file = "regex-2026.1.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0057de9eaef45783ff69fa94ae9f0fd906d629d0bd4c3217048f46d1daa32e9b"},
{file = "regex-2026.1.15-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc7cd0b2be0f0269283a45c0d8b2c35e149d1319dcb4a43c9c3689fa935c1ee6"},
{file = "regex-2026.1.15-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8db052bbd981e1666f09e957f3790ed74080c2229007c1dd67afdbf0b469c48b"},
{file = "regex-2026.1.15-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:343db82cb3712c31ddf720f097ef17c11dab2f67f7a3e7be976c4f82eba4e6df"},
{file = "regex-2026.1.15-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:55e9d0118d97794367309635df398bdfd7c33b93e2fdfa0b239661cd74b4c14e"},
{file = "regex-2026.1.15-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:008b185f235acd1e53787333e5690082e4f156c44c87d894f880056089e9bc7c"},
{file = "regex-2026.1.15-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fd65af65e2aaf9474e468f9e571bd7b189e1df3a61caa59dcbabd0000e4ea839"},
{file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f42e68301ff4afee63e365a5fc302b81bb8ba31af625a671d7acb19d10168a8c"},
{file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f7792f27d3ee6e0244ea4697d92b825f9a329ab5230a78c1a68bd274e64b5077"},
{file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:dbaf3c3c37ef190439981648ccbf0c02ed99ae066087dd117fcb616d80b010a4"},
{file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:adc97a9077c2696501443d8ad3fa1b4fc6d131fc8fd7dfefd1a723f89071cf0a"},
{file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:069f56a7bf71d286a6ff932a9e6fb878f151c998ebb2519a9f6d1cee4bffdba3"},
{file = "regex-2026.1.15-cp39-cp39-win32.whl", hash = "sha256:ea4e6b3566127fda5e007e90a8fd5a4169f0cf0619506ed426db647f19c8454a"},
{file = "regex-2026.1.15-cp39-cp39-win_amd64.whl", hash = "sha256:cda1ed70d2b264952e88adaa52eea653a33a1b98ac907ae2f86508eb44f65cdc"},
{file = "regex-2026.1.15-cp39-cp39-win_arm64.whl", hash = "sha256:b325d4714c3c48277bfea1accd94e193ad6ed42b4bad79ad64f3b8f8a31260a5"},
{file = "regex-2026.1.15.tar.gz", hash = "sha256:164759aa25575cbc0651bef59a0b18353e54300d79ace8084c818ad8ac72b7d5"},
] ]
[[package]] [[package]]
@@ -631,13 +643,13 @@ files = [
[[package]] [[package]]
name = "whitenoise" name = "whitenoise"
version = "6.11.0" version = "6.12.0"
description = "Radically simplified static file serving for WSGI applications" description = "Radically simplified static file serving for WSGI applications"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.10"
files = [ files = [
{file = "whitenoise-6.11.0-py3-none-any.whl", hash = "sha256:b2aeb45950597236f53b5342b3121c5de69c8da0109362aee506ce88e022d258"}, {file = "whitenoise-6.12.0-py3-none-any.whl", hash = "sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2"},
{file = "whitenoise-6.11.0.tar.gz", hash = "sha256:0f5bfce6061ae6611cd9396a8231e088722e4fc67bc13a111be74c738d99375f"}, {file = "whitenoise-6.12.0.tar.gz", hash = "sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad"},
] ]
[package.extras] [package.extras]
@@ -646,4 +658,4 @@ brotli = ["brotli"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.12" python-versions = "^3.12"
content-hash = "3d7a7f2fe8ec78993616e707e29e96503f134bd1cec48cac7f6dd47814863f4f" content-hash = "fb71f5e7011418134e09152004d990412cf923433f675398e5e99439bf9653b9"

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/favicon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -1,53 +1 @@
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="50" height="46" viewBox="0 0 1512 1386" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><defs><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="1476.52" y1="1395.4" x2="1034.86" y2="872.762"><stop offset="0" stop-color="#ef7f1a"></stop><stop offset=".349" stop-color="#ffed00"></stop><stop offset=".949" stop-color="#cc6f3c"></stop></linearGradient><linearGradient id="B" gradientUnits="userSpaceOnUse" x1="1023.98" y1="1127.45" x2="575.667" y2="596.929"><stop offset="0" stop-color="#ef7f1a"></stop><stop offset=".059" stop-color="#cc6f3c"></stop><stop offset=".671" stop-color="#ffed00"></stop><stop offset="1" stop-color="#cc6f3c"></stop></linearGradient><linearGradient id="C" href="#A" x1="1398.9" y1="1160.31" x2="613.548" y2="230.976"></linearGradient><linearGradient id="D" href="#A" x1="805.595" y1="948.571" x2="21.024" y2="20.143"></linearGradient><linearGradient id="E" gradientUnits="userSpaceOnUse" x1="381.071" y1="958.69" x2="89.071" y2="613.119"><stop offset="0" stop-color="#ef7f1a"></stop><stop offset=".831" stop-color="#ffed00"></stop><stop offset=".949" stop-color="#cc6f3c"></stop></linearGradient></defs><path d="M746 677L405 970c21-19 37-44 49-75 14-37 20-83 20-137 0-47-6-92-19-135s-32-82-58-118-57-67-95-95c-38-27-84-50-139-68L27 298c-6-2-12-2-18 1-1 0-2 1-3 2L351 5c1-1 2-2 4-2 6-3 12-3 18-1l136 44c55 18 101 41 139 68s69 59 95 95c25 36 45 75 58 118s19 88 19 135c0 54-7 100-20 137-12 34-30 60-53 79z" fill="url(#D)"></path><path d="M1005 1173c12-10 22-22 30-35 10-16 18-34 25-54 6-20 11-41 14-65 3-23 4-48 4-73 0-50-6-97-17-141-12-44-29-83-51-119-23-35-51-66-85-92s-74-47-119-62c-48-16-90-22-126-17-36 4-66 17-91 40l345-295c24-22 54-34 90-39 36-4 78 2 126 17 45 15 85 35 119 62 34 26 63 57 85 92 23 35 40 75 51 119s17 90 17 141c0 26-2 50-4 73-3 23-8 45-14 65s-15 38-25 54c-9 14-20 27-33 37l-341 292z" fill="url(#C)"></path><path d="M409 739c0-38-5-74-14-109-10-35-24-68-44-97-20-30-45-56-75-79s-69-42-116-58l-99-33v538l100 33c44 14 81 21 111 20s56-9 76-25 35-40 46-71c10-31 15-71 15-118zm65 18c0 54-7 100-20 137-14 37-34 65-60 84-27 19-59 30-98 31s-86-7-140-24L29 943c-6-2-12-6-18-13S1 914 1 902V319c0-12 3-19 10-22 6-3 12-3 18-1l136 44c55 18 101 41 139 68s69 59 95 95c25 36 45 75 58 118s19 88 19 135z" fill="#ae4a84" fill-rule="nonzero"></path><path d="M1343 884l-333 285c-3 3-7 6-10 9 26 30 49 54 68 73l48 43c13 10 22 18 29 23s12 9 15 13 5 8 6 13 2 10 2 17c0 5 0 10-1 13s-2 6-4 8l-2 2 344-294c1 0 1-1 2-2 2-2 3-5 4-8 1-4 1-8 1-13 0-7-1-12-2-17s-3-9-6-13-8-8-15-13-17-13-29-23c-13-10-29-25-48-43-20-19-42-43-68-73z" fill="url(#A)"></path><path d="M1015 931c0-38-4-75-11-111s-19-69-36-99-38-57-66-80c-27-23-61-41-102-54-40-13-74-17-101-11-28 6-50 18-67 38-17 19-29 44-37 75s-11 65-11 101c0 39 3 77 10 113s18 69 35 99c16 30 38 57 66 80 27 23 62 41 103 55 41 13 75 17 103 11s50-19 67-39 29-45 36-76 11-65 11-102zm153 430c0 5 0 10-1 13s-2 6-4 8-3 3-5 3c-2 1-3 0-5 0-4-1-12-7-26-16-13-10-29-22-48-39-19-16-39-35-61-57s-44-46-66-72c-17 6-39 9-65 9-27 0-58-6-94-18-48-16-89-37-124-63-35-27-63-57-85-93-22-35-38-75-49-119-10-44-16-91-16-142s6-95 18-132 29-67 53-88 53-34 89-38 78 2 126 17c45 15 85 35 119 62 34 26 63 57 85 92 23 35 40 75 51 119s17 90 17 141c0 26-2 50-4 73-3 23-8 45-14 65s-15 38-25 54-22 29-36 40c26 30 49 54 68 73l48 43c13 10 22 18 29 23s12 9 15 13 5 8 6 13 2 10 2 17z" fill="#ae4a84" fill-rule="nonzero"></path><path d="M1015 931c0-38-4-75-11-111s-19-69-36-99-38-57-66-80c-27-23-61-41-102-54-40-13-74-17-101-11-28 6-50 18-67 38-17 19-29 44-37 75s-11 65-11 101c0 39 3 77 10 113s18 69 35 99c16 30 38 57 66 80 27 23 62 41 103 55 41 13 75 17 103 11s50-19 67-39 29-45 36-76 11-65 11-102z" fill="url(#B)"></path><path d="M61 900l330-283c1 4 2 8 4 12 9 35 14 72 14 109 0 48-5 87-15 118s-25 55-46 71c-20 16-45 24-76 25s-67-6-111-20L61 899z" fill="url(#E)"></path><path d="M61 900l330-283c-9-31-23-59-40-85-20-30-45-56-75-79s-69-42-116-58l-99-33v538z" fill="#ffd900"></path></svg>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X6 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="120px" height="120px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 1512 1386"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil6 {fill:#FFD900}
.fil2 {fill:#AE4A84;fill-rule:nonzero}
.fil3 {fill:url(#id0)}
.fil4 {fill:url(#id1)}
.fil1 {fill:url(#id2)}
.fil0 {fill:url(#id3)}
.fil5 {fill:url(#id4)}
]]>
</style>
<linearGradient id="id0" gradientUnits="userSpaceOnUse" x1="1476.52" y1="1395.4" x2="1034.86" y2="872.762">
<stop offset="0" style="stop-color:#EF7F1A"/>
<stop offset="0.34902" style="stop-color:#FFED00"/>
<stop offset="0.94902" style="stop-color:#CC6F3C"/>
<stop offset="1" style="stop-color:#CC6F3C"/>
</linearGradient>
<linearGradient id="id1" gradientUnits="userSpaceOnUse" x1="1023.98" y1="1127.45" x2="575.667" y2="596.929">
<stop offset="0" style="stop-color:#EF7F1A"/>
<stop offset="0.0588235" style="stop-color:#CC6F3C"/>
<stop offset="0.670588" style="stop-color:#FFED00"/>
<stop offset="1" style="stop-color:#CC6F3C"/>
</linearGradient>
<linearGradient id="id2" gradientUnits="userSpaceOnUse" xlink:href="#id0" x1="1398.9" y1="1160.31" x2="613.548" y2="230.976">
</linearGradient>
<linearGradient id="id3" gradientUnits="userSpaceOnUse" xlink:href="#id0" x1="805.595" y1="948.571" x2="21.0238" y2="20.1429">
</linearGradient>
<linearGradient id="id4" gradientUnits="userSpaceOnUse" x1="381.071" y1="958.69" x2="89.0714" y2="613.119">
<stop offset="0" style="stop-color:#EF7F1A"/>
<stop offset="0.831373" style="stop-color:#FFED00"/>
<stop offset="0.94902" style="stop-color:#CC6F3C"/>
<stop offset="1" style="stop-color:#CC6F3C"/>
</linearGradient>
</defs>
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<path class="fil0" d="M746 677l-341 293c21,-19 37,-44 49,-75 14,-37 20,-83 20,-137 0,-47 -6,-92 -19,-135 -13,-43 -32,-82 -58,-118 -26,-36 -57,-67 -95,-95 -38,-27 -84,-50 -139,-68l-136 -44c-6,-2 -12,-2 -18,1 -1,0 -2,1 -3,2l345 -296c1,-1 2,-2 4,-2 6,-3 12,-3 18,-1l136 44c55,18 101,41 139,68 38,27 69,59 95,95 25,36 45,75 58,118 13,43 19,88 19,135 0,54 -7,100 -20,137 -12,34 -30,60 -53,79z"/>
<path class="fil1" d="M1005 1173c12,-10 22,-22 30,-35 10,-16 18,-34 25,-54 6,-20 11,-41 14,-65 3,-23 4,-48 4,-73 0,-50 -6,-97 -17,-141 -12,-44 -29,-83 -51,-119 -23,-35 -51,-66 -85,-92 -34,-26 -74,-47 -119,-62 -48,-16 -90,-22 -126,-17 -36,4 -66,17 -91,40l345 -295c24,-22 54,-34 90,-39 36,-4 78,2 126,17 45,15 85,35 119,62 34,26 63,57 85,92 23,35 40,75 51,119 11,44 17,90 17,141 0,26 -2,50 -4,73 -3,23 -8,45 -14,65 -6,20 -15,38 -25,54 -9,14 -20,27 -33,37l-341 292z"/>
<path class="fil2" d="M409 739c0,-38 -5,-74 -14,-109 -10,-35 -24,-68 -44,-97 -20,-30 -45,-56 -75,-79 -30,-23 -69,-42 -116,-58l-99 -33 0 538 100 33c44,14 81,21 111,20 30,-1 56,-9 76,-25 20,-16 35,-40 46,-71 10,-31 15,-71 15,-118zm65 18c0,54 -7,100 -20,137 -14,37 -34,65 -60,84 -27,19 -59,30 -98,31 -39,1 -86,-7 -140,-24l-127 -42c-6,-2 -12,-6 -18,-13 -6,-7 -10,-16 -10,-28l0 -583c0,-12 3,-19 10,-22 6,-3 12,-3 18,-1l136 44c55,18 101,41 139,68 38,27 69,59 95,95 25,36 45,75 58,118 13,43 19,88 19,135z"/>
<path class="fil3" d="M1343 884l-333 285c-3,3 -7,6 -10,9 26,30 49,54 68,73 20,18 36,33 48,43 13,10 22,18 29,23 7,5 12,9 15,13 3,4 5,8 6,13 1,5 2,10 2,17 0,5 0,10 -1,13 -1,3 -2,6 -4,8 -1,1 -1,1 -2,2l344 -294c1,0 1,-1 2,-2 2,-2 3,-5 4,-8 1,-4 1,-8 1,-13 0,-7 -1,-12 -2,-17 -1,-5 -3,-9 -6,-13 -3,-4 -8,-8 -15,-13 -7,-5 -17,-13 -29,-23 -13,-10 -29,-25 -48,-43 -20,-19 -42,-43 -68,-73z"/>
<path class="fil2" d="M1015 931c0,-38 -4,-75 -11,-111 -7,-36 -19,-69 -36,-99 -17,-30 -38,-57 -66,-80 -27,-23 -61,-41 -102,-54 -40,-13 -74,-17 -101,-11 -28,6 -50,18 -67,38 -17,19 -29,44 -37,75 -8,31 -11,65 -11,101 0,39 3,77 10,113 7,36 18,69 35,99 16,30 38,57 66,80 27,23 62,41 103,55 41,13 75,17 103,11 28,-6 50,-19 67,-39 17,-20 29,-45 36,-76 7,-31 11,-65 11,-102zm153 430c0,5 0,10 -1,13 -1,3 -2,6 -4,8 -2,2 -3,3 -5,3 -2,1 -3,0 -5,0 -4,-1 -12,-7 -26,-16 -13,-10 -29,-22 -48,-39 -19,-16 -39,-35 -61,-57 -22,-22 -44,-46 -66,-72 -17,6 -39,9 -65,9 -27,0 -58,-6 -94,-18 -48,-16 -89,-37 -124,-63 -35,-27 -63,-57 -85,-93 -22,-35 -38,-75 -49,-119 -10,-44 -16,-91 -16,-142 0,-51 6,-95 18,-132 12,-37 29,-67 53,-88 24,-21 53,-34 89,-38 36,-4 78,2 126,17 45,15 85,35 119,62 34,26 63,57 85,92 23,35 40,75 51,119 11,44 17,90 17,141 0,26 -2,50 -4,73 -3,23 -8,45 -14,65 -6,20 -15,38 -25,54 -10,16 -22,29 -36,40 26,30 49,54 68,73 20,18 36,33 48,43 13,10 22,18 29,23 7,5 12,9 15,13 3,4 5,8 6,13 1,5 2,10 2,17z"/>
<path class="fil4" d="M1015 931c0,-38 -4,-75 -11,-111 -7,-36 -19,-69 -36,-99 -17,-30 -38,-57 -66,-80 -27,-23 -61,-41 -102,-54 -40,-13 -74,-17 -101,-11 -28,6 -50,18 -67,38 -17,19 -29,44 -37,75 -8,31 -11,65 -11,101 0,39 3,77 10,113 7,36 18,69 35,99 16,30 38,57 66,80 27,23 62,41 103,55 41,13 75,17 103,11 28,-6 50,-19 67,-39 17,-20 29,-45 36,-76 7,-31 11,-65 11,-102z"/>
<path class="fil5" d="M61 900l330 -283c1,4 2,8 4,12 9,35 14,72 14,109 0,48 -5,87 -15,118 -10,31 -25,55 -46,71 -20,16 -45,24 -76,25 -31,1 -67,-6 -111,-20l-100 -33z"/>
<path class="fil6" d="M61 900l330 -283c-9,-31 -23,-59 -40,-85 -20,-30 -45,-56 -75,-79 -30,-23 -69,-42 -116,-58l-99 -33 0 538z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,26 +1,14 @@
# DicQuo # DicQuo
User-Agent: * User-Agent: *
Allow: / Allow: /
Disallow: /admin/
Disallow: /*?tag= Disallow: /*?tag=
Disallow: /*? Disallow: /*?
# Optimize for Yandex # Optimize for Yandex
Clean-param: tag / Clean-param: tag /
# AI and LLM bots settings
# OpenAI GPT
# User-agent: GPTBot
# Disallow:
# Common Crawl (used by many AI models)
# User-agent: CCBot
# Disallow:
# Google Bard/Gemini
# User-agent: Google-Extended
# Disallow:
Host: dq.cube2.ru Host: dq.cube2.ru
Sitemap: https://dq.cube2.ru/sitemap.xml Sitemap: https://dq.cube2.ru/sitemap.xml
# Ссылка на файл для ИИ-моделей
Link: /llms.txt

23
public/site.webmanifest Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "DicQuo",
"short_name": "DicQuo",
"description": "Коллекция цитат и афоризмов.",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#ffffff",
"icons": [
{
"src": "/favicon.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
}

View File

@@ -1,8 +1,8 @@
@charset "utf-8"; @charset "utf-8";
body { body {
margin: 0; margin: 0;
min-height: 100vh; min-height: 100vmin;
min-width: 100vw; min-width: 100vmin;
background-color: #111; /* Изначально темный фон */ background-color: #111; /* Изначально темный фон */
opacity: 0; /* Скрываем контент до расчета цвета */ opacity: 0; /* Скрываем контент до расчета цвета */
transition: opacity 0.9s ease-in-out; /* Очень плавное появление */ transition: opacity 0.9s ease-in-out; /* Очень плавное появление */
@@ -13,11 +13,11 @@ header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 1vh 4vw; padding: 1vmin 4vmin;
} }
header > #logo { header > #logo {
margin-top: 1vh; margin-top: 1vmin;
float: left; float: left;
} }
@@ -69,8 +69,8 @@ header > nav > #stats-menu > b {
header > nav > #stats-menu > p { header > nav > #stats-menu > p {
font-style: italic; display: inline-block; font-style: italic; display: inline-block;
margin: 0 1vw; margin: 0 1vmin;
padding-right: 1vw; padding-right: 1vmin;
border-right: 1px dotted silver; border-right: 1px dotted silver;
} }
@@ -120,23 +120,18 @@ header > nav > #stats-menu > a:hover {
/* MAIN ARTICLE CONTENT */ /* MAIN ARTICLE CONTENT */
main { main {
/*justify-content: space-between;*/ padding: 1vmin 8vmin;
/*align-items: center;*/
padding: 1vh 8vw;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
min-height: 60vh; min-height: 60vmin;
/*width: 90%;*/
/*max-width: 1200px;*/
/*margin: 0 auto;*/
} }
main > article { main > article {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 2vw; gap: 2vmin;
} }
main > article > figure { main > article > figure {
@@ -170,18 +165,18 @@ main > article > figure > cite { /* Автор цитаты */
} }
main > article > div { main > article > div {
flex: 0 0 30vw; flex: 0 0 30vmax;
display: flex; display: flex;
justify-content: center; justify-content: center;
width: 30vw; width: 30vmax;
text-align: right; text-align: right;
margin-bottom: 10vh; margin-bottom: 10vmin;
} }
main > article > div > div { main > article > div > div {
width: 26vmax; width: 26vmax;
height: 26vmax; height: 26vmax;
padding: 0.5vw; padding: 0.5vmin;
border-radius: 50%; border-radius: 50%;
} }
@@ -200,13 +195,13 @@ main > article > div > div > div > img {
/* НАВИГАЦИЯ (ТЕГИ И ДАЛЕЕ) В КОНЦЕ */ /* НАВИГАЦИЯ (ТЕГИ И ДАЛЕЕ) В КОНЦЕ */
nav { nav {
padding: 1vh 4vw; padding: 1vmin 4vmin;
} }
nav > div { nav > div {
color: silver; color: silver;
font-size: 1.5vmin; font-size: 1.5vmin;
line-height: 1.9vmin; line-height: 1.9vmin;
padding-top: 7vh; padding: 7vmin 0 4vmin 0;
} }
nav > div a { nav > div a {
text-decoration: none; text-decoration: none;
@@ -250,7 +245,7 @@ footer {
bottom: 0; bottom: 0;
left: 0; left: 0;
width: 100%; width: 100%;
padding: 2vh 4vw; padding: 2vmin 4vmin;
color: silver; /* Мягкий серый цвет текста */ color: silver; /* Мягкий серый цвет текста */
background-color: rgba(30, 30, 30, 0.8); /* Темный полупрозрачный фон */ background-color: rgba(30, 30, 30, 0.8); /* Темный полупрозрачный фон */
backdrop-filter: blur(5px); /* Эффект матового стекла (современно и медитативно) */ backdrop-filter: blur(5px); /* Эффект матового стекла (современно и медитативно) */
@@ -262,12 +257,12 @@ footer {
footer small { footer small {
display: inline-block; display: inline-block;
margin-right: 2vw; margin-right: 2vmin;
letter-spacing: 0.05em; /* Немного воздуха в тексте */ letter-spacing: 0.05em; /* Немного воздуха в тексте */
} }
footer button { footer button {
padding: 0.5vh 1.5vw; padding: 0.5vmin 1.5vmin;
background: transparent; background: transparent;
color: silver; color: silver;
border: 1px solid silver; border: 1px solid silver;
@@ -292,18 +287,42 @@ footer button:hover {
main > article > div { main > article > div {
flex: 0 0 auto; flex: 0 0 auto;
margin-bottom: 2vh; margin: 8vmin 0 2vmin 0;
}
main > article > div {
width: 80vmin;
}
main > article > div > div {
width: 36vmax;
height: 36vmax;
}
main > article > div > div > div > img {
height: 36vmax;
}
/* Для мобильных путь иконка будет поменьше и поскромнее */
header > #logo a > img {
width:25px;
height:23px;
} }
} }
/* --- ВЫРАВНИВАНИЕ СИМВОЛОВ ВИСЯЧЕЙ ПУНКТУАЦИИ (Hanging Punctuation) ТИПОГРАФА ETPGRF --- */ /* --- ВЫРАВНИВАНИЕ СИМВОЛОВ ВИСЯЧЕЙ ПУНКТУАЦИИ (Hanging Punctuation) ТИПОГРАФА ETPGRF --- */
/* --- В ПРОЕКТЕ ТОЛЬКО ЛЕВЫЕ ВИСЯЧИЕ СИМВОЛЫ (выравнивание по левому краю) --- */ /* --- В ПРОЕКТЕ ТОЛЬКО ЛЕВЫЕ ВИСЯЧИЕ СИМВОЛЫ (выравнивание по левому краю) --- */
.etp-laquo {margin-left: -0.44em;} /* « */ .etp-laquo { margin-left: -0.49em; } /* « */
.etp-ldquo, .etp-bdquo { margin-left: -0.4em;} /* “ */ .etp-ldquo, .etp-bdquo { margin-left: -0.4em; } /* “ */
.etp-lsquo {margin-left: -0.22em;} /* */ .etp-lsquo { margin-left: -0.22em; } /* */
.etp-lpar, .etp-lsqb, .etp-lcub {margin-left: -0.25em;}/* ( [ { */ .etp-lpar, .etp-lsqb, .etp-lcub { margin-left: -0.23em; } /* ( [ { */
/* компенсирующие пробелы для левых висячих символов */
.etp-sp-laquo { padding-right: 0.49em; }
.etp-sp-ldquo, .etp-sp-bdquo { padding-right: 0.4em; }
.etp-sp-lsquo { padding-right: 0.22em; }
.etp-sp-lpar, .etp-sp-lsqb, .etp-sp-lcub { padding-right: 0.23em; }
/* --- СЧЕТЧИКИ (СКРЫТЫЙ ПИКСЕЛЬ) --- */ /* --- СЧЕТЧИКИ (СКРЫТЫЙ ПИКСЕЛЬ) top.mail.ru и Яндекс.Метрика --- */
.counter-pixel { .counter-pixel {
border: 0; border: 0;
position: absolute; position: absolute;

View File

@@ -0,0 +1,60 @@
/* Select2 (django-select2) dark theme compatibility for Django Admin.
We intentionally scope to dark mode only and lean on Django Admin CSS variables. */
@media (prefers-color-scheme: dark) {
html:not([data-theme="light"]) .select2-container--default .select2-selection--single,
html:not([data-theme="light"]) .select2-container--default .select2-selection--multiple {
background: var(--body-bg, #1e1e1e) !important;
color: var(--body-fg, #e6e6e6) !important;
border-color: var(--border-color, #3a3a3a) !important;
}
}
html[data-theme="dark"] .select2-container--default .select2-selection--single,
html[data-theme="dark"] .select2-container--default .select2-selection--multiple {
background: var(--body-bg, #1e1e1e) !important;
color: var(--body-fg, #e6e6e6) !important;
border-color: var(--border-color, #3a3a3a) !important;
}
html[data-theme="dark"] .select2-container--default .select2-selection__rendered {
color: var(--body-fg, #e6e6e6) !important;
}
html[data-theme="dark"] .select2-container--default .select2-search--inline .select2-search__field,
html[data-theme="dark"] .select2-container--default .select2-search--dropdown .select2-search__field {
background: transparent !important;
color: var(--body-fg, #e6e6e6) !important;
}
html[data-theme="dark"] .select2-container--default .select2-selection--multiple .select2-selection__choice {
background: rgba(255, 255, 255, 0.08) !important;
border-color: rgba(255, 255, 255, 0.14) !important;
color: var(--body-fg, #e6e6e6) !important;
}
html[data-theme="dark"] .select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
color: var(--body-fg, #e6e6e6) !important;
opacity: 0.8;
}
html[data-theme="dark"] .select2-dropdown {
background: var(--body-bg, #1e1e1e) !important;
color: var(--body-fg, #e6e6e6) !important;
border-color: var(--border-color, #3a3a3a) !important;
}
html[data-theme="dark"] .select2-container--default .select2-results__option {
color: var(--body-fg, #e6e6e6) !important;
}
html[data-theme="dark"] .select2-container--default .select2-results__option--highlighted.select2-results__option--selectable {
background: rgba(255, 255, 255, 0.10) !important;
color: var(--body-fg, #ffffff) !important;
}
html[data-theme="dark"] .select2-container--default .select2-results__option--selected {
background: rgba(255, 255, 255, 0.06) !important;
color: var(--body-fg, #e6e6e6) !important;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -18,5 +18,25 @@ _tmr.push({id: "3744288", type: "pageView", start: (new Date()).getTime()});
f(); f();
} }
})(document, window, "tmr-code"); })(document, window, "tmr-code");
// //Rating Mail.ru counter // Yandex.Metrika counter
(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?id=106953063', 'ym');
ym(106953063, 'init', {ssr:true, webvisor:true, clickmap:true, ecommerce:"dataLayer", referrer: document.referrer, url: location.href, accurateTrackBounce:true, trackLinks:true});
// Google Analytics (GA4) counter
(function() {
var gaScript = document.createElement('script');
gaScript.async = true;
gaScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-WTJM8J9YL5';
document.head.appendChild(gaScript);
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
// Делаем функцию глобально доступной, если понадобится вызывать её из других скриптов
window.gtag = gtag;
gtag('js', new Date());
gtag('config', 'G-WTJM8J9YL5');
})();

View File

@@ -1,53 +1 @@
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="50" height="46" viewBox="0 0 1512 1386" shape-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd"><defs><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="1476.52" y1="1395.4" x2="1034.86" y2="872.762"><stop offset="0" stop-color="#ef7f1a"/><stop offset=".349" stop-color="#ffed00"/><stop offset=".949" stop-color="#cc6f3c"/></linearGradient><linearGradient id="B" gradientUnits="userSpaceOnUse" x1="1023.98" y1="1127.45" x2="575.667" y2="596.929"><stop offset="0" stop-color="#ef7f1a"/><stop offset=".059" stop-color="#cc6f3c"/><stop offset=".671" stop-color="#ffed00"/><stop offset="1" stop-color="#cc6f3c"/></linearGradient><linearGradient id="C" href="#A" x1="1398.9" y1="1160.31" x2="613.548" y2="230.976"/><linearGradient id="D" href="#A" x1="805.595" y1="948.571" x2="21.024" y2="20.143"/><linearGradient id="E" gradientUnits="userSpaceOnUse" x1="381.071" y1="958.69" x2="89.071" y2="613.119"><stop offset="0" stop-color="#ef7f1a"/><stop offset=".831" stop-color="#ffed00"/><stop offset=".949" stop-color="#cc6f3c"/></linearGradient></defs><path d="M746 677L405 970c21-19 37-44 49-75 14-37 20-83 20-137 0-47-6-92-19-135s-32-82-58-118-57-67-95-95c-38-27-84-50-139-68L27 298c-6-2-12-2-18 1-1 0-2 1-3 2L351 5c1-1 2-2 4-2 6-3 12-3 18-1l136 44c55 18 101 41 139 68s69 59 95 95c25 36 45 75 58 118s19 88 19 135c0 54-7 100-20 137-12 34-30 60-53 79z" fill="url(#D)"/><path d="M1005 1173c12-10 22-22 30-35 10-16 18-34 25-54 6-20 11-41 14-65 3-23 4-48 4-73 0-50-6-97-17-141-12-44-29-83-51-119-23-35-51-66-85-92s-74-47-119-62c-48-16-90-22-126-17-36 4-66 17-91 40l345-295c24-22 54-34 90-39 36-4 78 2 126 17 45 15 85 35 119 62 34 26 63 57 85 92 23 35 40 75 51 119s17 90 17 141c0 26-2 50-4 73-3 23-8 45-14 65s-15 38-25 54c-9 14-20 27-33 37l-341 292z" fill="url(#C)"/><path d="M409 739c0-38-5-74-14-109-10-35-24-68-44-97-20-30-45-56-75-79s-69-42-116-58l-99-33v538l100 33c44 14 81 21 111 20s56-9 76-25 35-40 46-71c10-31 15-71 15-118zm65 18c0 54-7 100-20 137-14 37-34 65-60 84-27 19-59 30-98 31s-86-7-140-24L29 943c-6-2-12-6-18-13S1 914 1 902V319c0-12 3-19 10-22 6-3 12-3 18-1l136 44c55 18 101 41 139 68s69 59 95 95c25 36 45 75 58 118s19 88 19 135z" fill="#ae4a84" fill-rule="nonzero"/><path d="M1343 884l-333 285c-3 3-7 6-10 9 26 30 49 54 68 73l48 43c13 10 22 18 29 23s12 9 15 13 5 8 6 13 2 10 2 17c0 5 0 10-1 13s-2 6-4 8l-2 2 344-294c1 0 1-1 2-2 2-2 3-5 4-8 1-4 1-8 1-13 0-7-1-12-2-17s-3-9-6-13-8-8-15-13-17-13-29-23c-13-10-29-25-48-43-20-19-42-43-68-73z" fill="url(#A)"/><path d="M1015 931c0-38-4-75-11-111s-19-69-36-99-38-57-66-80c-27-23-61-41-102-54-40-13-74-17-101-11-28 6-50 18-67 38-17 19-29 44-37 75s-11 65-11 101c0 39 3 77 10 113s18 69 35 99c16 30 38 57 66 80 27 23 62 41 103 55 41 13 75 17 103 11s50-19 67-39 29-45 36-76 11-65 11-102zm153 430c0 5 0 10-1 13s-2 6-4 8-3 3-5 3c-2 1-3 0-5 0-4-1-12-7-26-16-13-10-29-22-48-39-19-16-39-35-61-57s-44-46-66-72c-17 6-39 9-65 9-27 0-58-6-94-18-48-16-89-37-124-63-35-27-63-57-85-93-22-35-38-75-49-119-10-44-16-91-16-142s6-95 18-132 29-67 53-88 53-34 89-38 78 2 126 17c45 15 85 35 119 62 34 26 63 57 85 92 23 35 40 75 51 119s17 90 17 141c0 26-2 50-4 73-3 23-8 45-14 65s-15 38-25 54-22 29-36 40c26 30 49 54 68 73l48 43c13 10 22 18 29 23s12 9 15 13 5 8 6 13 2 10 2 17z" fill="#ae4a84" fill-rule="nonzero"/><path d="M1015 931c0-38-4-75-11-111s-19-69-36-99-38-57-66-80c-27-23-61-41-102-54-40-13-74-17-101-11-28 6-50 18-67 38-17 19-29 44-37 75s-11 65-11 101c0 39 3 77 10 113s18 69 35 99c16 30 38 57 66 80 27 23 62 41 103 55 41 13 75 17 103 11s50-19 67-39 29-45 36-76 11-65 11-102z" fill="url(#B)"/><path d="M61 900l330-283c1 4 2 8 4 12 9 35 14 72 14 109 0 48-5 87-15 118s-25 55-46 71c-20 16-45 24-76 25s-67-6-111-20L61 899z" fill="url(#E)"/><path d="M61 900l330-283c-9-31-23-59-40-85-20-30-45-56-75-79s-69-42-116-58l-99-33v538z" fill="#ffd900"/></svg>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW X6 -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="50px" height="46px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 1512 1386"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil6 {fill:#FFD900}
.fil2 {fill:#AE4A84;fill-rule:nonzero}
.fil3 {fill:url(#id0)}
.fil4 {fill:url(#id1)}
.fil1 {fill:url(#id2)}
.fil0 {fill:url(#id3)}
.fil5 {fill:url(#id4)}
]]>
</style>
<linearGradient id="id0" gradientUnits="userSpaceOnUse" x1="1476.52" y1="1395.4" x2="1034.86" y2="872.762">
<stop offset="0" style="stop-color:#EF7F1A"/>
<stop offset="0.34902" style="stop-color:#FFED00"/>
<stop offset="0.94902" style="stop-color:#CC6F3C"/>
<stop offset="1" style="stop-color:#CC6F3C"/>
</linearGradient>
<linearGradient id="id1" gradientUnits="userSpaceOnUse" x1="1023.98" y1="1127.45" x2="575.667" y2="596.929">
<stop offset="0" style="stop-color:#EF7F1A"/>
<stop offset="0.0588235" style="stop-color:#CC6F3C"/>
<stop offset="0.670588" style="stop-color:#FFED00"/>
<stop offset="1" style="stop-color:#CC6F3C"/>
</linearGradient>
<linearGradient id="id2" gradientUnits="userSpaceOnUse" xlink:href="#id0" x1="1398.9" y1="1160.31" x2="613.548" y2="230.976">
</linearGradient>
<linearGradient id="id3" gradientUnits="userSpaceOnUse" xlink:href="#id0" x1="805.595" y1="948.571" x2="21.0238" y2="20.1429">
</linearGradient>
<linearGradient id="id4" gradientUnits="userSpaceOnUse" x1="381.071" y1="958.69" x2="89.0714" y2="613.119">
<stop offset="0" style="stop-color:#EF7F1A"/>
<stop offset="0.831373" style="stop-color:#FFED00"/>
<stop offset="0.94902" style="stop-color:#CC6F3C"/>
<stop offset="1" style="stop-color:#CC6F3C"/>
</linearGradient>
</defs>
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<path class="fil0" d="M746 677l-341 293c21,-19 37,-44 49,-75 14,-37 20,-83 20,-137 0,-47 -6,-92 -19,-135 -13,-43 -32,-82 -58,-118 -26,-36 -57,-67 -95,-95 -38,-27 -84,-50 -139,-68l-136 -44c-6,-2 -12,-2 -18,1 -1,0 -2,1 -3,2l345 -296c1,-1 2,-2 4,-2 6,-3 12,-3 18,-1l136 44c55,18 101,41 139,68 38,27 69,59 95,95 25,36 45,75 58,118 13,43 19,88 19,135 0,54 -7,100 -20,137 -12,34 -30,60 -53,79z"/>
<path class="fil1" d="M1005 1173c12,-10 22,-22 30,-35 10,-16 18,-34 25,-54 6,-20 11,-41 14,-65 3,-23 4,-48 4,-73 0,-50 -6,-97 -17,-141 -12,-44 -29,-83 -51,-119 -23,-35 -51,-66 -85,-92 -34,-26 -74,-47 -119,-62 -48,-16 -90,-22 -126,-17 -36,4 -66,17 -91,40l345 -295c24,-22 54,-34 90,-39 36,-4 78,2 126,17 45,15 85,35 119,62 34,26 63,57 85,92 23,35 40,75 51,119 11,44 17,90 17,141 0,26 -2,50 -4,73 -3,23 -8,45 -14,65 -6,20 -15,38 -25,54 -9,14 -20,27 -33,37l-341 292z"/>
<path class="fil2" d="M409 739c0,-38 -5,-74 -14,-109 -10,-35 -24,-68 -44,-97 -20,-30 -45,-56 -75,-79 -30,-23 -69,-42 -116,-58l-99 -33 0 538 100 33c44,14 81,21 111,20 30,-1 56,-9 76,-25 20,-16 35,-40 46,-71 10,-31 15,-71 15,-118zm65 18c0,54 -7,100 -20,137 -14,37 -34,65 -60,84 -27,19 -59,30 -98,31 -39,1 -86,-7 -140,-24l-127 -42c-6,-2 -12,-6 -18,-13 -6,-7 -10,-16 -10,-28l0 -583c0,-12 3,-19 10,-22 6,-3 12,-3 18,-1l136 44c55,18 101,41 139,68 38,27 69,59 95,95 25,36 45,75 58,118 13,43 19,88 19,135z"/>
<path class="fil3" d="M1343 884l-333 285c-3,3 -7,6 -10,9 26,30 49,54 68,73 20,18 36,33 48,43 13,10 22,18 29,23 7,5 12,9 15,13 3,4 5,8 6,13 1,5 2,10 2,17 0,5 0,10 -1,13 -1,3 -2,6 -4,8 -1,1 -1,1 -2,2l344 -294c1,0 1,-1 2,-2 2,-2 3,-5 4,-8 1,-4 1,-8 1,-13 0,-7 -1,-12 -2,-17 -1,-5 -3,-9 -6,-13 -3,-4 -8,-8 -15,-13 -7,-5 -17,-13 -29,-23 -13,-10 -29,-25 -48,-43 -20,-19 -42,-43 -68,-73z"/>
<path class="fil2" d="M1015 931c0,-38 -4,-75 -11,-111 -7,-36 -19,-69 -36,-99 -17,-30 -38,-57 -66,-80 -27,-23 -61,-41 -102,-54 -40,-13 -74,-17 -101,-11 -28,6 -50,18 -67,38 -17,19 -29,44 -37,75 -8,31 -11,65 -11,101 0,39 3,77 10,113 7,36 18,69 35,99 16,30 38,57 66,80 27,23 62,41 103,55 41,13 75,17 103,11 28,-6 50,-19 67,-39 17,-20 29,-45 36,-76 7,-31 11,-65 11,-102zm153 430c0,5 0,10 -1,13 -1,3 -2,6 -4,8 -2,2 -3,3 -5,3 -2,1 -3,0 -5,0 -4,-1 -12,-7 -26,-16 -13,-10 -29,-22 -48,-39 -19,-16 -39,-35 -61,-57 -22,-22 -44,-46 -66,-72 -17,6 -39,9 -65,9 -27,0 -58,-6 -94,-18 -48,-16 -89,-37 -124,-63 -35,-27 -63,-57 -85,-93 -22,-35 -38,-75 -49,-119 -10,-44 -16,-91 -16,-142 0,-51 6,-95 18,-132 12,-37 29,-67 53,-88 24,-21 53,-34 89,-38 36,-4 78,2 126,17 45,15 85,35 119,62 34,26 63,57 85,92 23,35 40,75 51,119 11,44 17,90 17,141 0,26 -2,50 -4,73 -3,23 -8,45 -14,65 -6,20 -15,38 -25,54 -10,16 -22,29 -36,40 26,30 49,54 68,73 20,18 36,33 48,43 13,10 22,18 29,23 7,5 12,9 15,13 3,4 5,8 6,13 1,5 2,10 2,17z"/>
<path class="fil4" d="M1015 931c0,-38 -4,-75 -11,-111 -7,-36 -19,-69 -36,-99 -17,-30 -38,-57 -66,-80 -27,-23 -61,-41 -102,-54 -40,-13 -74,-17 -101,-11 -28,6 -50,18 -67,38 -17,19 -29,44 -37,75 -8,31 -11,65 -11,101 0,39 3,77 10,113 7,36 18,69 35,99 16,30 38,57 66,80 27,23 62,41 103,55 41,13 75,17 103,11 28,-6 50,-19 67,-39 17,-20 29,-45 36,-76 7,-31 11,-65 11,-102z"/>
<path class="fil5" d="M61 900l330 -283c1,4 2,8 4,12 9,35 14,72 14,109 0,48 -5,87 -15,118 -10,31 -25,55 -46,71 -20,16 -45,24 -76,25 -31,1 -67,-6 -111,-20l-100 -33z"/>
<path class="fil6" d="M61 900l330 -283c-9,-31 -23,-59 -40,-85 -20,-30 -45,-56 -75,-79 -30,-23 -69,-42 -116,-58l-99 -33 0 538z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View File

@@ -12,11 +12,12 @@ django = "^6.0.2"
django-taggit = "^6.1.0" django-taggit = "^6.1.0"
pillow = "^12.1.1" pillow = "^12.1.1"
pytils = "^0.4.4" pytils = "^0.4.4"
etpgrf = "^0.1.4" etpgrf = "^0.1.6"
django-environ = "^0.12.1" django-environ = "^0.13.0"
whitenoise = "^6.11.0" whitenoise = "^6.11.0"
gunicorn = "^25.1.0" gunicorn = "^25.1.0"
tqdm = "^4.67.3" tqdm = "^4.67.3"
django-select2 = "^8.4.8"
[build-system] [build-system]