This commit is contained in:
EDeev 2025-12-06 12:54:15 +03:00
parent 9980762ae6
commit 5f2ebd8792
7 changed files with 525 additions and 264 deletions

View file

@ -1,156 +1,177 @@
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', '*****')
DEBUG = os.environ.get('DJANGO_DEBUG', 'False') == 'True'
ALLOWED_HOSTS = ['deev.space', 'deev.su', 'www.deev.space', 'www.deev.su', 'localhost', '127.0.0.1']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sitemaps',
'main.apps.MainConfig',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'dspace.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'main.context_processors.site_settings',
],
},
},
]
WSGI_APPLICATION = 'dspace.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
AUTH_USER_MODEL = 'main.CustomUser'
AUTH_PASSWORD_VALIDATORS = []
LANGUAGE_CODE = 'ru-ru'
TIME_ZONE = 'Europe/Moscow'
USE_I18N = True
USE_TZ = True
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static']
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Yandex SmartCaptcha Settings
SMARTCAPTCHA_CLIENT_KEY = os.environ.get('SMARTCAPTCHA_CLIENT_KEY', '*****')
SMARTCAPTCHA_SERVER_KEY = os.environ.get('SMARTCAPTCHA_SERVER_KEY', '*****')
# Telegram Bot Settings
TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN', '')
TELEGRAM_CHANNEL_ID = os.environ.get('TELEGRAM_CHANNEL_ID', '')
# VK API Settings
VK_ACCESS_TOKEN = os.environ.get('VK_ACCESS_TOKEN', '')
VK_GROUP_ID = os.environ.get('VK_GROUP_ID', '')
# Email Settings
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.yandex.ru')
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 465))
EMAIL_USE_SSL = True
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '')
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'egor@deev.space')
CONTACT_EMAIL = os.environ.get('CONTACT_EMAIL', 'egor@deev.space')
# Site Settings
SITE_NAME = 'deev.space'
SITE_AUTHOR = 'Деев Егор Викторович'
SITE_DESCRIPTION = 'Персональный сайт backend-разработчика'
# Session Settings
SESSION_COOKIE_AGE = 31536000 # 1 year
SESSION_COOKIE_SECURE = not DEBUG
CSRF_COOKIE_SECURE = not DEBUG
# Security Settings (Production)
if not DEBUG:
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': BASE_DIR / 'logs' / 'django.log',
'formatter': 'verbose',
},
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
},
'loggers': {
'django': {
'handlers': ['console'] if DEBUG else ['file', 'console'],
'level': 'INFO',
},
'main': {
'handlers': ['console'] if DEBUG else ['file', 'console'],
'level': 'DEBUG' if DEBUG else 'INFO',
},
},
}
# Create logs directory
import os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', '*****')
DEBUG = os.environ.get('DJANGO_DEBUG', 'False') == 'True'
ALLOWED_HOSTS = ['deev.space', 'deev.su', 'www.deev.space', 'www.deev.su', 'localhost', '127.0.0.1']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sitemaps',
'main.apps.MainConfig',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'dspace.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'main.context_processors.site_settings',
],
},
},
]
WSGI_APPLICATION = 'dspace.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
AUTH_USER_MODEL = 'main.CustomUser'
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
LANGUAGE_CODE = 'ru-ru'
TIME_ZONE = 'Europe/Moscow'
USE_I18N = True
USE_TZ = True
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static']
STATIC_ROOT = BASE_DIR / 'staticfiles'
# Хеширование статических файлов в production
if not DEBUG:
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Yandex SmartCaptcha Settings
SMARTCAPTCHA_CLIENT_KEY = os.environ.get('SMARTCAPTCHA_CLIENT_KEY', '*****')
SMARTCAPTCHA_SERVER_KEY = os.environ.get('SMARTCAPTCHA_SERVER_KEY', '*****')
# Telegram Bot Settings
TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN', '')
TELEGRAM_CHANNEL_ID = os.environ.get('TELEGRAM_CHANNEL_ID', '')
# VK API Settings
VK_ACCESS_TOKEN = os.environ.get('VK_ACCESS_TOKEN', '')
VK_GROUP_ID = os.environ.get('VK_GROUP_ID', '')
# Email Settings
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.yandex.ru')
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 465))
EMAIL_USE_SSL = True
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '')
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'egor@deev.space')
CONTACT_EMAIL = os.environ.get('CONTACT_EMAIL', 'egor@deev.space')
# Site Settings
SITE_NAME = 'deev.space'
SITE_AUTHOR = 'Деев Егор Викторович'
SITE_DESCRIPTION = 'Персональный сайт backend-разработчика'
# Session Settings
SESSION_COOKIE_AGE = 31536000 # 1 year
SESSION_COOKIE_SECURE = not DEBUG
CSRF_COOKIE_SECURE = not DEBUG
# Security Settings (Production)
if not DEBUG:
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': BASE_DIR / 'logs' / 'django.log',
'formatter': 'verbose',
},
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
},
'loggers': {
'django': {
'handlers': ['console'] if DEBUG else ['file', 'console'],
'level': 'INFO',
},
'main': {
'handlers': ['console'] if DEBUG else ['file', 'console'],
'level': 'DEBUG' if DEBUG else 'INFO',
},
},
}
# Create logs directory
(BASE_DIR / 'logs').mkdir(exist_ok=True)

View file

@ -399,46 +399,273 @@
}
.article-body {
font-size: 1.1rem;
line-height: 1.8;
font-size: 1.05rem;
line-height: 1.7;
color: var(--text-secondary);
text-align: justify;
text-justify: inter-word;
hyphens: auto;
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
}
.article-body p {
margin-bottom: var(--space-lg);
margin-bottom: 1rem;
margin-top: 0;
text-align: justify;
}
.article-body h2,
.article-body h3,
.article-body h4,
.article-body h5,
.article-body h6 {
text-align: left;
}
.article-body h2 {
color: var(--text-primary);
margin-top: 2.5rem;
margin-bottom: 1rem;
font-size: 1.75rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--border-color);
}
.article-body h3 {
color: var(--text-primary);
margin-top: 2rem;
margin-bottom: 0.75rem;
font-size: 1.35rem;
}
.article-body h4 {
color: var(--text-primary);
margin-top: var(--space-2xl);
margin-bottom: var(--space-md);
margin-top: 1.5rem;
margin-bottom: 0.75rem;
font-size: 1.15rem;
}
.article-body ul,
.article-body ol {
margin-bottom: var(--space-lg);
padding-left: var(--space-xl);
margin-bottom: 1.25rem;
margin-top: 0.75rem;
padding-left: 2rem;
text-align: justify;
}
.article-body li {
margin-bottom: var(--space-sm);
margin-bottom: 0.5rem;
line-height: 1.6;
text-align: justify;
}
.article-body li p {
margin-bottom: 0.5rem;
}
.article-body img {
max-width: 100%;
height: auto;
border-radius: var(--radius-md);
margin: var(--space-lg) 0;
margin: 1.5rem 0;
box-shadow: var(--shadow-md);
}
.article-body blockquote {
border-left: 4px solid var(--primary-color);
padding-left: var(--space-lg);
margin: var(--space-lg) 0;
font-style: italic;
padding: 1rem 1.5rem;
margin: 1.5rem 0;
background: var(--bg-card);
border-radius: var(--radius-sm);
font-style: normal;
color: var(--text-secondary);
text-align: justify;
}
.article-body blockquote p {
text-align: justify;
}
.article-body blockquote p:last-child {
margin-bottom: 0;
}
.article-body hr {
border: none;
height: 1px;
background: var(--border-color);
margin: 2rem 0;
}
.article-body table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
background: var(--bg-card);
border-radius: var(--radius-md);
overflow: hidden;
}
.article-body th,
.article-body td {
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
.article-body th {
background: var(--bg-elevated);
color: var(--text-primary);
font-weight: 600;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.article-body tr:last-child td {
border-bottom: none;
}
.article-body tr:hover {
background: var(--bg-elevated);
}
.article-body code {
font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
background: var(--bg-elevated);
padding: 0.2em 0.5em;
border-radius: var(--radius-xs);
font-size: 0.9em;
color: var(--primary-light);
border: 1px solid var(--border-color);
}
.article-body pre {
position: relative;
background: var(--bg-darker);
padding: 1.25rem;
border-radius: var(--radius-md);
overflow-x: auto;
margin: 1.5rem 0;
border: 1px solid var(--border-color);
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.2);
}
.article-body pre code {
background: none;
padding: 0;
border: none;
color: #e0e0e0;
font-size: 0.9rem;
line-height: 1.6;
display: block;
}
.article-body pre:hover .code-copy-btn {
opacity: 1;
}
.code-copy-btn {
position: absolute;
top: 0.75rem;
right: 0.75rem;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
padding: 0.4rem 0.75rem;
font-size: 0.8rem;
color: var(--text-secondary);
cursor: pointer;
transition: var(--transition);
opacity: 0;
display: flex;
align-items: center;
gap: 0.35rem;
}
.code-copy-btn:hover {
background: var(--primary-muted);
border-color: var(--primary-color);
color: var(--primary-color);
}
.code-copy-btn.copied {
background: rgba(34, 197, 94, 0.15);
border-color: var(--success-color);
color: var(--success-color);
}
.code-copy-btn i {
font-size: 0.85rem;
}
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 1rem;
background: var(--bg-elevated);
border-bottom: 1px solid var(--border-color);
border-radius: var(--radius-md) var(--radius-md) 0 0;
margin-bottom: -1px;
}
.code-language {
font-size: 0.75rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
.article-body pre.has-header {
border-radius: 0 0 var(--radius-md) var(--radius-md);
margin-top: 0;
}
.article-body h2,
.article-body h3 {
position: relative;
}
.header-anchor {
position: absolute;
left: -1.5rem;
opacity: 0;
transition: opacity 0.2s;
color: var(--text-muted);
text-decoration: none;
font-size: 0.8em;
}
.article-body h2:hover .header-anchor,
.article-body h3:hover .header-anchor {
opacity: 1;
}
.header-anchor:hover {
color: var(--primary-color);
}
.article-body pre,
.article-body pre code {
text-align: left;
hyphens: none;
-webkit-hyphens: none;
}
.article-body table,
.article-body th,
.article-body td {
text-align: left;
}
.article-body figcaption,
.carousel-caption,
.collage-caption {
text-align: center;
}
/* Article Footer */
@ -689,7 +916,7 @@
gap: var(--space-md);
}
.comment {
div.comment {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-xl);

View file

@ -13,6 +13,8 @@
initBackToTop();
initMessages();
initContactForm();
initCodeBlocks();
addHeaderAnchors();
});
// ===== AOS Animation =====
@ -453,4 +455,98 @@
});
};
// ===== Code Copy Functionality =====
function initCodeBlocks() {
const codeBlocks = document.querySelectorAll('.article-body pre');
codeBlocks.forEach((pre) => {
// Проверяем, не добавлена ли уже кнопка
if (pre.querySelector('.code-copy-btn')) {
return;
}
const codeElement = pre.querySelector('code');
if (!codeElement) return;
// Создаём кнопку копирования
const copyButton = document.createElement('button');
copyButton.className = 'code-copy-btn';
copyButton.type = 'button';
copyButton.setAttribute('aria-label', 'Копировать код');
copyButton.innerHTML = '<i class="fas fa-copy"></i><span class="copy-text">Копировать</span>';
// Обработчик клика
copyButton.addEventListener('click', async function(e) {
e.preventDefault();
// Получаем текст кода без HTML-тегов
const codeText = codeElement.textContent || codeElement.innerText;
try {
await navigator.clipboard.writeText(codeText);
// Изменяем визуальное состояние кнопки
copyButton.innerHTML = '<i class="fas fa-check"></i><span class="copy-text">Сохранено!</span>';
copyButton.classList.add('copied');
// Возвращаем исходное состояние через 2 секунды
setTimeout(() => {
copyButton.innerHTML = '<i class="fas fa-copy"></i><span class="copy-text">Копировать</span>';
copyButton.classList.remove('copied');
}, 2000);
} catch (err) {
console.error('Ошибка при копировании:', err);
// Fallback для старых браузеров
const textArea = document.createElement('textarea');
textArea.value = codeText;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
copyButton.innerHTML = '<i class="fas fa-check"></i><span class="copy-text">Сохранено!</span>';
copyButton.classList.add('copied');
setTimeout(() => {
copyButton.innerHTML = '<i class="fas fa-copy"></i><span class="copy-text">Копировать</span>';
copyButton.classList.remove('copied');
}, 2000);
} catch (err2) {
showNotification('Не удалось скопировать код', 'error');
}
document.body.removeChild(textArea);
}
});
// Добавляем кнопку в блок кода
pre.style.position = 'relative';
pre.appendChild(copyButton);
});
}
function addHeaderAnchors() {
const headers = document.querySelectorAll('.article-body h2, .article-body h3');
headers.forEach(header => {
const id = header.textContent
.toLowerCase()
.replace(/[^a-zа-яё0-9]+/g, '-')
.replace(/^-|-$/g, '');
header.id = id;
const anchor = document.createElement('a');
anchor.className = 'header-anchor';
anchor.href = `#${id}`;
anchor.innerHTML = '<i class="fas fa-link"></i>';
anchor.setAttribute('aria-label', 'Ссылка на раздел');
header.appendChild(anchor);
});
}
})();

View file

@ -6,9 +6,7 @@
<div class="auth-card" data-aos="fade-up">
<div class="auth-header">
<a href="{% url 'index' %}" class="logo">
<span class="logo-deev">deev</span>
<span class="logo-dot"></span>
<span class="logo-space">space</span>
<img src="{% static 'img/logo.png' %}" alt="deev.space" style="max-width: 130px; height: auto;">
</a>
<h1 class="auth-title">Вход в аккаунт</h1>
<p class="auth-subtitle">Войдите, чтобы оставлять комментарии и оценивать статьи</p>

View file

@ -6,9 +6,7 @@
<div class="auth-card" data-aos="fade-up">
<div class="auth-header">
<a href="{% url 'index' %}" class="logo">
<span class="logo-deev">deev</span>
<span class="logo-dot"></span>
<span class="logo-space">space</span>
<img src="{% static 'img/logo.png' %}" alt="deev.space" style="max-width: 130px; height: auto;">
</a>
<h1 class="auth-title">Регистрация</h1>
<p class="auth-subtitle">Создайте аккаунт для участия в обсуждениях</p>

View file

@ -91,7 +91,7 @@
<!-- Article Content -->
<div class="article-body" data-aos="fade-up" data-aos-delay="300">
{{ article.post|safe|linebreaks }}
{{ article.post|safe }}
</div>
<!-- Image Gallery -->

View file

@ -24,7 +24,6 @@
<meta property="og:description" content="{{ page_description|default:global_settings.site_description }}">
<meta property="og:type" content="{% block og_type %}website{% endblock %}">
<meta property="og:url" content="https://deev.space{{ request.path }}">
<meta property="og:image" content="{% block og_image %}{% static 'images/og-default.jpg' %}{% endblock %}">
<meta property="og:site_name" content="{{ global_settings.site_name }}">
<meta property="og:locale" content="ru_RU">
@ -32,7 +31,6 @@
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ page_title|default:global_settings.owner_name }}">
<meta name="twitter:description" content="{{ page_description|default:global_settings.site_description }}">
<meta name="twitter:image" content="{% block twitter_image %}{% static 'images/og-default.jpg' %}{% endblock %}">
<!-- Canonical URL -->
<link rel="canonical" href="https://deev.space{{ request.path }}">
@ -55,6 +53,10 @@
<!-- AOS Animation -->
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<!-- Prism.js для подсветки синтаксиса -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.css">
<!-- Custom Styles -->
<link rel="stylesheet" href="{% static 'css/variables.css' %}">
@ -255,93 +257,6 @@
</div>
</div>
</footer>
<!-- Footer -->
<!-- <footer class="footer">
<div class="container">
<div class="footer-grid">
<div class="footer-brand">
<a href="{% url 'index' %}" class="logo">
</a>
<p>{{ global_settings.site_description|default:"Персональный сайт backend-разработчика. Создаю надёжные и масштабируемые решения на Python и Django." }}</p>
</div>
<div class="footer-nav">
<h4>Навигация</h4>
<ul class="footer-links">
<li><a href="{% url 'about' %}">Обо мне</a></li>
<li><a href="{% url 'projects' %}">Проекты</a></li>
<li><a href="{% url 'blog' %}">Блог</a></li>
<li><a href="{% url 'achievements' %}">Достижения</a></li>
<li><a href="{% url 'contacts' %}">Контакты</a></li>
</ul>
</div>
<div class="footer-contact">
<h4>Контакты</h4>
<ul class="footer-links">
<li>
<a href="mailto:{{ global_settings.owner_email }}">
<i class="fas fa-envelope"></i>
{{ global_settings.owner_email }}
</a>
</li>
{% if global_settings.owner_phone %}
<li>
<a href="tel:{{ global_settings.owner_phone|cut:' '|cut:'('|cut:')'|cut:'-' }}">
<i class="fas fa-phone"></i>
{{ global_settings.owner_phone }}
</a>
</li>
{% endif %}
<li>
<i class="fas fa-map-marker-alt"></i>
{{ global_settings.owner_city }}, Россия
</li>
</ul>
</div>
<div class="footer-social">
<h4>Социальные сети</h4>
<div class="footer-socials">
{% if global_settings.telegram_url %}
<a href="{{ global_settings.telegram_url }}" target="_blank" rel="noopener noreferrer" class="footer-social-link" aria-label="Telegram">
<i class="fab fa-telegram"></i>
</a>
{% endif %}
{% if global_settings.github_url %}
<a href="{{ global_settings.github_url }}" target="_blank" rel="noopener noreferrer" class="footer-social-link" aria-label="GitHub">
<i class="fab fa-github"></i>
</a>
{% endif %}
{% if global_settings.vk_url %}
<a href="{{ global_settings.vk_url }}" target="_blank" rel="noopener noreferrer" class="footer-social-link" aria-label="VKontakte">
<i class="fab fa-vk"></i>
</a>
{% endif %}
{% if global_settings.linkedin_url %}
<a href="{{ global_settings.linkedin_url }}" target="_blank" rel="noopener noreferrer" class="footer-social-link" aria-label="LinkedIn">
<i class="fab fa-linkedin"></i>
</a>
{% endif %}
<a href="mailto:{{ global_settings.owner_email }}" class="footer-social-link" aria-label="Email">
<i class="fas fa-envelope"></i>
</a>
</div>
</div>
</div>
<div class="footer-bottom">
<p class="footer-copyright">
&copy; 2024-{% now "Y" %} {{ global_settings.owner_name }}. Все права защищены.
</p>
<p class="footer-made">
Сделано с <i class="fas fa-heart" style="color: var(--primary-color);"></i> на Django
</p>
</div>
</div>
</footer> -->
<!-- Back to Top -->
<button class="back-to-top" id="backToTop" aria-label="Наверх">
@ -351,6 +266,12 @@
<!-- Scripts -->
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script src="{% static 'js/main.js' %}"></script>
<!-- Prism.js скрипты -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/normalize-whitespace/prism-normalize-whitespace.min.js"></script>
{% block extra_js %}{% endblock %}
</body>