Какой-то пиздец

This commit is contained in:
NikDizell 2025-11-19 21:57:49 +03:00
commit 39213ac57e
29 changed files with 6336 additions and 22 deletions

View File

@ -26,6 +26,15 @@ class Recall(models.Model):
verbose_name_plural = 'Отзывы'
ordering = ['time_create', 'title']
def get_seo_title(self):
return f"Отзыв от {self.title} | Программист 1С"
def get_seo_description(self):
if self.content:
clean_content = self.content[:160].replace('\n', ' ').strip()
return f"Отзыв о работе программиста 1С от {self.title}. {clean_content}..."
return f"Отзыв клиента {self.title} о работе программиста 1С Николая Сердюк"
class Competence(models.Model):
title = models.CharField(max_length=255, verbose_name='Программист')
@ -67,6 +76,23 @@ class Solution(models.Model):
verbose_name_plural = 'Проекты'
ordering = ['time_create', 'title']
def get_seo_title(self):
"""Генерирует SEO-заголовок для проекта"""
return f"Проект: {self.title} | Автоматизация 1С"
def get_seo_description(self):
"""Генерирует SEO-описание для проекта"""
if self.description:
clean_desc = self.description[:160].replace('\n', ' ').strip()
return f"Проект автоматизации: {self.title}. {clean_desc}..."
return f"Реализация проекта {self.title} - программист 1С Николай Сердюк"
def get_meta_keywords(self):
"""Автоматические ключевые слова для проекта"""
base_keywords = ["проект 1С", "автоматизация 1С", "внедрение 1С"]
title_words = self.title.lower().split()
return base_keywords + title_words
class Home(models.Model):
title = models.CharField(max_length=255, verbose_name='Наименование')

View File

@ -4,15 +4,15 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<title>{{title}} - Программист 1С</title>
<!-- Основные мета-теги -->
<meta name="description" content="{% block meta_description %}Профессиональный программист 1С с более чем 10-летним опытом. Разработка, интеграция и оптимизация систем 1С.{% endblock %}">
<meta name="keywords" content="{% block meta_keywords %}программист 1С, разработка 1С, интеграция 1С, оптимизация 1С, 1С предприятие{% endblock %}">
<title>{{title}}</title>
<!-- Основные мета-теги -->
<meta name="description" content="{% block meta_description %}{{ meta_description|default:'Профессиональный программист 1С с более чем 10-летним опытом. Разработка, интеграция и оптимизация систем 1С.' }}{% endblock %}">
<meta name="keywords" content="{% block meta_keywords %}{{ meta_keywords|default:'программист 1С, разработка 1С, интеграция 1С, оптимизация 1С, 1С предприятие' }}{% endblock %}">
<meta name="author" content="Николай Сердюк">
<!-- Open Graph для соцсетей -->
<meta property="og:title" content="{{title}} - Программист 1С">
<meta property="og:description" content="Профессиональный программист 1С с более чем 10-летним опытом">
<meta property="og:title" content="{{title}}">
<meta property="og:description" content="{% block og_description %}{{ meta_description|default:'Профессиональный программист 1С с более чем 10-летним опытом' }}{% endblock %}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.build_absolute_uri }}">
<meta property="og:image" content="{% static 'programmer/images/og-image.jpg' %}">
@ -20,8 +20,8 @@
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{title}} - Программист 1С">
<meta name="twitter:description" content="Профессиональный программист 1С с более чем 10-летним опытом">
<meta name="twitter:title" content="{{title}}">
<meta name="twitter:description" content="{% block twitter_description %}{{ meta_description|default:'Профессиональный программист 1С с более чем 10-летним опытом' }}{% endblock %}">
<meta name="twitter:image" content="{% static 'programmer/images/og-image.jpg' %}">
<!-- Дополнительные SEO-теги -->
@ -551,5 +551,28 @@
{% block extra_js %}
<!-- Дополнительные JS файлы для конкретных страниц -->
{% endblock %}
<!-- В recall.html после основного контента -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Review",
"itemReviewed": {
"@type": "Service",
"name": "Услуги программиста 1С"
},
"author": {
"@type": "Organization",
"name": "ООО «РОВЕН-Регионы»"
},
"reviewRating": {
"@type": "Rating",
"ratingValue": "5",
"bestRating": "5"
},
"datePublished": "2025-11-13",
"description": "Выражаю благодарность программисту 1С Николаю Сердюк за профессиональную работу и качественное решение поставленных задач..."
}
</script>
</body>
</html>

View File

@ -20,7 +20,7 @@
<div class="competence-scan-wrapper">
<div class="competence-scan-container">
<img src="{{ p.photo.url }}"
alt="{{ p.title }}"
alt="Сертификат 1С: {{ p.title }} - {{ p.content|striptags }}"
class="competence-scan"
onclick="openCompetenceModal('{{ p.photo.url }}', '{{ p.title }}')">
<div class="scan-hint">

View File

@ -1,6 +1,7 @@
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% load static %}
{% load seo_tags %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'programmer/css/recall.css' %}">
@ -8,13 +9,37 @@
{% block content %}
<div class="page-header">
<h1 class="page-title">Отзывы</h1>
<p class="page-subtitle">Мнения клиентов и партнеров</p>
<h1 class="page-title">Отзывы клиентов</h1>
<p class="page-subtitle">Реальные отзывы о работе программиста 1С</p>
</div>
<div class="recall-grid">
{% for p in posts %}
<div class="modern-card fade-in">
<!-- Добавляем микроразметку для отзыва -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Review",
"itemReviewed": {
"@type": "Service",
"name": "Услуги программиста 1С"
},
"author": {
"@type": "Organization",
"name": "{{ p.title }}"
},
"reviewRating": {
"@type": "Rating",
"ratingValue": "5",
"bestRating": "5"
},
"datePublished": "{{ p.time_create|date:'Y-m-d' }}",
"description": "{{ p.content|striptags|truncatewords:50 }}"
}
</script>
<div class="recall-item">
<div class="recall-header">
<div class="recall-info">

View File

@ -6,6 +6,7 @@ Sitemap: {{ request.scheme }}://{{ request.get_host }}/sitemap.xml
# Запрещаем служебные разделы
Disallow: /admin/
Disallow: /media/cache/
Disallow: /static/admin/
Disallow: /callback/
Disallow: /api/

View File

@ -1,21 +1,40 @@
{% extends 'programmer/base.html' %}
{% load django_bootstrap5 %}
{% load static %}
{% load seo_tags %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'programmer/css/solution-accordion.css' %}">
{% endblock %}
{% block content %}
<h1>{{title}}</h1>
<div class="page-header">
<h1 class="page-title">Проекты автоматизации 1С</h1>
<p class="page-subtitle">Реализованные решения и кейсы по автоматизации бизнес-процессов</p>
</div>
<div class="improved-list">
{% autoescape off %}
{% for p in posts %}
<li class="fade-in">
<li class="modern-card fade-in">
<div class="content-card">
<h2>{{p.title}}</h2>
<!-- Добавляем микроразметку для проекта -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CreativeWork",
"name": "{{ p.title }}",
"description": "{{ p.description|striptags|truncatewords:30 }}",
"author": {
"@type": "Person",
"name": "Николай Сердюк"
},
"datePublished": "{{ p.time_create|date:'Y-m-d' }}"
}
</script>
<div class="solution-accordion">
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
@ -52,6 +71,13 @@
<p class="first">Опубликовано: {{p.time_create|date:"d.m.Y"}}</p>
</div>
</div>
{% empty %}
<div class="modern-card text-center fade-in">
<h3>🚀 Проекты в разработке</h3>
<p class="card-subtitle">Скоро здесь появятся новые кейсы автоматизации</p>
</div>
</li>
{% endfor %}
{% endautoescape %}

View File

@ -0,0 +1,21 @@
from django import template
from django.utils.html import strip_tags
register = template.Library()
@register.simple_tag
def generate_meta_description(obj, default=""):
"""Генерирует meta description для объектов"""
if hasattr(obj, 'get_seo_description'):
return obj.get_seo_description()
elif hasattr(obj, 'content'):
clean_content = strip_tags(obj.content)[:160]
return clean_content + '...' if len(clean_content) > 160 else clean_content
return default
@register.simple_tag
def generate_meta_keywords(obj, default=""):
"""Генерирует meta keywords для объектов"""
if hasattr(obj, 'get_meta_keywords'):
return ', '.join(obj.get_meta_keywords())
return default

View File

@ -107,9 +107,9 @@ def index(request):
context = {
'posts': posts,
'menu': menu,
'title': "Главная страница",
'meta_description': "Профессиональный программист 1С с более чем 10-летним опытом. Разработка, интеграция и оптимизация систем 1С. Закажите консультацию.",
'meta_keywords': "программист 1С, разработка 1С, интеграция 1С, оптимизация 1С, 1С предприятие 8.3",
'title': "Программист 1С Николай Сердюк - разработка и сопровождение",
'meta_description': "Профессиональный программист 1С с более чем 10-летним опытом. Разработка, доработка, обновление и интеграция систем 1С. Сопровождение 1С.",
'meta_keywords': "программист 1С, разработка 1С, обновление 1С, сопровождение 1С, интеграция 1С, доработка 1С, 1С предприятие 8.3",
'form': CallbackForm()
}
return render(request, 'programmer/index.html', context=context)
@ -119,9 +119,9 @@ def index(request):
def about(request):
context = {
'menu': menu,
'title': "Обо мне - Программист 1С",
'meta_description': "Николай Сердюк - профессиональный программист 1С с более чем 10-летним опытом. Разработка, интеграция, оптимизация бизнес-процессов.",
'meta_keywords': "программист 1С Николай Сердюк, опыт работы 1С, компетенции 1С, проекты 1С"
'title': "Программист 1С Николай Сердюк - 10+ лет опыта | Услуги 1С",
'meta_description': "Николай Сердюк - сертифицированный программист 1С с 10+ лет опыта. Специализация: обновление 1С, разработка под ключ, интеграция, миграция с 1С 7.7.",
'meta_keywords': "программист 1С Николай Сердюк, обновление 1С, разработка 1С под ключ, интеграция 1С, сертифицированный 1С, миграция 1С 7.7"
}
return render(request, 'programmer/about.html', context=context)
@ -132,7 +132,9 @@ def solution(request):
context = {
'posts': posts,
'menu': menu,
'title': "Проекты"
'meta_description': "Реализованные проекты по автоматизации 1С: складской учет с ТСД, интеграция с оборудованием, миграция с 1С 7.7. Примеры работ и кейсы.",
'meta_keywords': "проекты 1С, автоматизация склада 1С, интеграция ТСД 1С, миграция 1С 7.7, кейсы 1С, примеры работ 1С",
'title': "Проекты автоматизации 1С | Реализованные кейсы и решения",
}
return render(request, 'programmer/solution.html', context=context)
@ -143,7 +145,9 @@ def ability(request):
context = {
'posts': posts,
'menu': menu,
'title': "Компетенции"
'title': "Сертификаты и компетенции 1С | Программист 1С Николай Сердюк",
'meta_description': "Сертификаты 1С: Профессионал по платформе 8.3 и БП 3.0. Подтвержденная квалификация программиста 1С с сертификатами фирмы 1С.",
'meta_keywords': "сертификаты 1С, 1С профессионал, компетенции 1С, квалификация программиста 1С, сертифицированный специалист 1С"
}
return render(request, 'programmer/competence.html', context=context)
@ -154,7 +158,9 @@ def recall(request):
context = {
'posts': posts,
'menu': menu,
'title': "Отзывы"
'title': "Отзывы клиентов о работе программиста 1С | Реальные кейсы",
'meta_description': "Реальные отзывы клиентов о работе программиста 1С Николая Сердюка. Отзывы от ООО «РОВЕН-Регионы» и других компаний.",
'meta_keywords': "отзывы программист 1С, рекомендации 1С, отзывы клиентов 1С, реальные кейсы 1С, отзыв ООО РОВЕН"
}
return render(request, 'programmer/recall.html', context=context)

View File

@ -0,0 +1,38 @@
/**
* @fileOverview CSS for jquery-autocomplete, the jQuery Autocompleter
* @author <a href="mailto:dylan@dyve.net">Dylan Verheul</a>
* @license MIT | GPL | Apache 2.0, see LICENSE.txt
* @see https://github.com/dyve/jquery-autocomplete
*/
.acResults {
padding: 0px;
border: 1px solid WindowFrame;
background-color: Window;
overflow: hidden;
}
.acResults ul {
margin: 0px;
padding: 0px;
list-style-position: outside;
list-style: none;
}
.acResults ul li {
margin: 0px;
padding: 2px 5px;
cursor: pointer;
display: block;
font: menu;
font-size: 12px;
overflow: hidden;
}
.acLoading {
background : url('../img/indicator.gif') right center no-repeat;
}
.acSelect {
background-color: Highlight;
color: HighlightText;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,116 @@
/**
* Ajax Queue Plugin
*/
/**
<script>
$(function(){
jQuery.ajaxQueue({
url: "test.php",
success: function(html){ jQuery("ul").append(html); }
});
jQuery.ajaxQueue({
url: "test.php",
success: function(html){ jQuery("ul").append(html); }
});
jQuery.ajaxSync({
url: "test.php",
success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
});
jQuery.ajaxSync({
url: "test.php",
success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
});
});
</script>
<ul style="position: absolute; top: 5px; right: 5px;"></ul>
*/
/*
* Queued Ajax requests.
* A new Ajax request won't be started until the previous queued
* request has finished.
*/
/*
* Synced Ajax requests.
* The Ajax request will happen as soon as you call this method, but
* the callbacks (success/error/complete) won't fire until all previous
* synced requests have been completed.
*/
(function(jQuery) {
var ajax = jQuery.ajax;
var pendingRequests = {};
var synced = [];
var syncedData = [];
jQuery.ajax = function(settings) {
// create settings for compatibility with ajaxSetup
settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings));
var port = settings.port;
switch(settings.mode) {
case "abort":
if ( pendingRequests[port] ) {
pendingRequests[port].abort();
}
return pendingRequests[port] = ajax.apply(this, arguments);
case "queue":
var _old = settings.complete;
settings.complete = function(){
if ( _old )
_old.apply( this, arguments );
jQuery([ajax]).dequeue("ajax" + port );;
};
jQuery([ ajax ]).queue("ajax" + port, function(){
ajax( settings );
});
return;
case "sync":
var pos = synced.length;
synced[ pos ] = {
error: settings.error,
success: settings.success,
complete: settings.complete,
done: false
};
syncedData[ pos ] = {
error: [],
success: [],
complete: []
};
settings.error = function(){ syncedData[ pos ].error = arguments; };
settings.success = function(){ syncedData[ pos ].success = arguments; };
settings.complete = function(){
syncedData[ pos ].complete = arguments;
synced[ pos ].done = true;
if ( pos == 0 || !synced[ pos-1 ] )
for ( var i = pos; i < synced.length && synced[i].done; i++ ) {
if ( synced[i].error ) synced[i].error.apply( jQuery, syncedData[i].error );
if ( synced[i].success ) synced[i].success.apply( jQuery, syncedData[i].success );
if ( synced[i].complete ) synced[i].complete.apply( jQuery, syncedData[i].complete );
synced[i] = null;
syncedData[i] = null;
}
};
}
return ajax.apply(this, arguments);
};
})((typeof window.jQuery == 'undefined' && typeof window.django != 'undefined')
? django.jQuery
: jQuery
);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
/*! Copyright (c) 2010 Brandon Aaron (http://brandon.aaron.sh/)
* Licensed under the MIT License (LICENSE.txt).
*
* Version 2.1.2
*/
(function($){
$.fn.bgiframe = ($.browser.msie && /msie 6\.0/i.test(navigator.userAgent) ? function(s) {
s = $.extend({
top : 'auto', // auto == .currentStyle.borderTopWidth
left : 'auto', // auto == .currentStyle.borderLeftWidth
width : 'auto', // auto == offsetWidth
height : 'auto', // auto == offsetHeight
opacity : true,
src : 'javascript:false;'
}, s);
var html = '<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+
'style="display:block;position:absolute;z-index:-1;'+
(s.opacity !== false?'filter:Alpha(Opacity=\'0\');':'')+
'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+
'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+
'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+
'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+
'"/>';
return this.each(function() {
if ( $(this).children('iframe.bgiframe').length === 0 )
this.insertBefore( document.createElement(html), this.firstChild );
});
} : function() { return this; });
// old alias
$.fn.bgIframe = $.fn.bgiframe;
function prop(n) {
return n && n.constructor === Number ? n + 'px' : n;
}
})((typeof window.jQuery == 'undefined' && typeof window.django != 'undefined') ? django.jQuery : jQuery);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,299 @@
/* competence.css - Стили для страницы компетенций */
/* Основные стили для страницы компетенций */
.competence-item {
display: flex;
gap: 2rem;
align-items: flex-start;
padding: 2rem;
background: var(--bg-card);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
border-left: 4px solid var(--secondary);
transition: var(--transition);
border: 1px solid var(--border-light);
}
.competence-item:hover {
transform: translateX(8px);
box-shadow: var(--shadow-xl);
border-color: var(--primary-light);
}
.competence-scan-wrapper {
flex-shrink: 0;
}
.competence-scan-container {
width: 280px;
cursor: pointer;
border-radius: var(--radius-lg);
overflow: hidden;
border: 2px solid var(--border-light);
transition: var(--transition);
position: relative;
box-shadow: var(--shadow-md);
}
.competence-scan-container:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: var(--shadow-xl);
border-color: var(--primary);
}
.competence-scan {
width: 100%;
height: auto;
display: block;
transition: var(--transition);
}
.competence-content {
flex: 1;
min-width: 0;
}
.competence-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 1rem;
background: var(--gradient-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.competence-description {
line-height: 1.7;
font-size: 1.05rem;
color: var(--text-primary);
}
.competence-description p {
margin-bottom: 1rem;
}
.competence-description p:last-child {
margin-bottom: 0;
}
.scan-hint {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
color: white;
padding: 1rem;
text-align: center;
opacity: 0;
transition: var(--transition);
transform: translateY(10px);
}
.competence-scan-container:hover .scan-hint {
opacity: 1;
transform: translateY(0);
}
/* Стили для модального окна с изображением компетенций */
.modal.competence-modal {
background-color: rgba(15, 19, 31, 0.95);
backdrop-filter: blur(10px);
}
.modal.competence-modal .modal-content {
background: transparent;
border: none;
box-shadow: none;
max-width: 95vw;
max-height: 95vh;
margin: 2% auto;
}
.modal.competence-modal .modal-header {
background: var(--bg-card);
border-bottom: 2px solid var(--border-light);
padding: 1.5rem 2rem;
}
.modal.competence-modal .modal-header h3 {
margin: 0;
color: var(--text-primary);
font-size: 1.25rem;
font-weight: 600;
background: var(--gradient-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.modal.competence-modal .modal-body {
padding: 1rem;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
}
.modal-image {
max-width: 90vw;
max-height: 80vh;
width: auto;
height: auto;
display: block;
margin: 0 auto;
border-radius: var(--radius-md);
box-shadow: var(--shadow-xl);
transition: all 0.3s ease;
}
/* Анимации для модального окна */
.modal.competence-modal {
transition: opacity 0.3s ease;
opacity: 0;
}
.modal.competence-modal.active {
opacity: 1;
}
.modal.competence-modal .modal-content {
transform: scale(0.7);
transition: transform 0.3s ease;
}
.modal.competence-modal.active .modal-content {
transform: scale(1);
}
/* Улучшенные тени и границы */
.competence-scan-container {
border: 2px solid var(--border-light);
box-shadow: var(--shadow-lg);
}
.competence-scan-container:hover {
border-color: var(--primary-light);
box-shadow: var(--shadow-xl);
}
/* Адаптивность для мобильных устройств */
@media (max-width: 768px) {
.competence-item {
flex-direction: column;
padding: 1.5rem;
gap: 1.5rem;
}
.competence-scan-container {
width: 100%;
max-width: 300px;
margin: 0 auto;
}
.competence-title {
font-size: 1.375rem;
text-align: center;
}
.modal-image {
max-width: 95vw;
max-height: 70vh;
}
.modal.competence-modal .modal-content {
margin: 10% auto;
}
.modal.competence-modal .modal-header {
padding: 1rem 1.5rem;
}
}
@media (max-width: 480px) {
.competence-item {
padding: 1.25rem;
}
.competence-scan-container {
max-width: 100%;
}
.competence-title {
font-size: 1.25rem;
}
.modal.competence-modal .modal-content {
margin: 5% auto;
max-width: 98vw;
}
.modal.competence-modal .modal-body {
padding: 0.5rem;
}
.modal.competence-modal .modal-header {
padding: 1rem;
}
.modal.competence-modal .modal-header h3 {
font-size: 1.1rem;
}
}
/* Стили для светлой темы */
@media (prefers-color-scheme: light) {
.modal.competence-modal {
background-color: rgba(0, 0, 0, 0.8);
}
.modal.competence-modal .modal-header {
background: var(--bg-primary);
}
.scan-hint {
background: linear-gradient(transparent, rgba(0,0,0,0.7));
}
.competence-item {
background: var(--bg-primary);
}
.competence-description {
color: var(--text-primary);
}
}
/* Улучшенные стили для сетки компетенций */
.competence-grid {
display: grid;
gap: 2rem;
}
.competence-grid .modern-card {
padding: 0;
overflow: hidden;
}
.competence-grid .modern-card::before {
height: 4px;
background: var(--gradient-secondary);
}
/* Анимации появления */
.fade-in {
animation: fadeInUp 0.8s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(40px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View File

@ -0,0 +1,228 @@
/* recall.css - Стили для страницы отзывов */
/* Основные стили для страницы отзывов */
.recall-item {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.recall-content {
display: flex;
gap: 2rem;
align-items: flex-start;
}
.recall-scan-wrapper {
flex-shrink: 0;
}
.recall-scan-container {
width: 280px;
cursor: pointer;
border-radius: var(--radius-lg);
overflow: hidden;
border: 2px solid var(--border-light);
transition: var(--transition);
position: relative;
box-shadow: var(--shadow-md);
}
.recall-scan-container:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: var(--shadow-xl);
border-color: var(--primary);
}
.recall-scan {
width: 100%;
height: auto;
display: block;
transition: var(--transition);
}
.recall-text {
flex: 1;
min-width: 0;
line-height: 1.7;
font-size: 1.05rem;
color: var(--text-primary);
}
.recall-text p {
margin-bottom: 1rem;
}
.recall-text p:last-child {
margin-bottom: 0;
}
.scan-hint {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
color: white;
padding: 1rem;
text-align: center;
opacity: 0;
transition: var(--transition);
transform: translateY(10px);
}
.recall-scan-container:hover .scan-hint {
opacity: 1;
transform: translateY(0);
}
/* Стили для модального окна с изображением */
.modal.image-modal {
background-color: rgba(15, 19, 31, 0.95);
backdrop-filter: blur(10px);
}
.modal.image-modal .modal-content {
background: transparent;
border: none;
box-shadow: none;
max-width: 95vw;
max-height: 95vh;
margin: 2% auto;
}
.modal.image-modal .modal-header {
background: var(--bg-card);
border-bottom: 2px solid var(--border-light);
padding: 1.5rem 2rem;
}
.modal.image-modal .modal-header h3 {
margin: 0;
color: var(--text-primary);
font-size: 1.25rem;
font-weight: 600;
background: var(--gradient-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.modal.image-modal .modal-body {
padding: 1rem;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
}
.modal-image {
max-width: 90vw;
max-height: 80vh;
width: auto;
height: auto;
display: block;
margin: 0 auto;
border-radius: var(--radius-md);
box-shadow: var(--shadow-xl);
transition: all 0.3s ease;
}
/* Анимации для модального окна */
.modal.image-modal {
transition: opacity 0.3s ease;
opacity: 0;
}
.modal.image-modal.active {
opacity: 1;
}
.modal.image-modal .modal-content {
transform: scale(0.7);
transition: transform 0.3s ease;
}
.modal.image-modal.active .modal-content {
transform: scale(1);
}
/* Улучшенные тени и границы */
.recall-scan-container {
border: 2px solid var(--border-light);
box-shadow: var(--shadow-lg);
}
.recall-scan-container:hover {
border-color: var(--primary-light);
box-shadow: var(--shadow-xl);
}
/* Адаптивность для мобильных устройств */
@media (max-width: 768px) {
.recall-content {
flex-direction: column;
gap: 1.5rem;
}
.recall-scan-container {
width: 100%;
max-width: 300px;
margin: 0 auto;
}
.recall-scan-wrapper {
order: -1;
}
.modal-image {
max-width: 95vw;
max-height: 70vh;
}
.modal.image-modal .modal-content {
margin: 10% auto;
}
.modal.image-modal .modal-header {
padding: 1rem 1.5rem;
}
}
@media (max-width: 480px) {
.recall-scan-container {
max-width: 100%;
}
.modal.image-modal .modal-content {
margin: 5% auto;
max-width: 98vw;
}
.modal.image-modal .modal-body {
padding: 0.5rem;
}
.modal.image-modal .modal-header {
padding: 1rem;
}
.modal.image-modal .modal-header h3 {
font-size: 1.1rem;
}
}
/* Стили для светлой темы */
@media (prefers-color-scheme: light) {
.modal.image-modal {
background-color: rgba(0, 0, 0, 0.8);
}
.modal.image-modal .modal-header {
background: var(--bg-primary);
}
.scan-hint {
background: linear-gradient(transparent, rgba(0,0,0,0.7));
}
}

View File

@ -0,0 +1,67 @@
/* solution-accordion.css */
.solution-accordion {
margin: 2rem 0;
}
.accordion-item {
background: var(--bg-primary);
border-radius: var(--radius-lg);
margin-bottom: 1rem;
border: 2px solid var(--border-light);
overflow: hidden;
transition: var(--transition);
}
.accordion-item:hover {
border-color: var(--primary-light);
}
.accordion-header {
padding: 1.5rem;
background: var(--gradient-primary);
color: white;
font-weight: 600;
font-size: 1.125rem;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: var(--transition);
user-select: none;
}
.accordion-header:hover {
background: var(--primary-dark);
}
.accordion-content {
padding: 0;
background: var(--bg-card);
line-height: 1.7;
color: var(--text-secondary);
max-height: 0;
overflow: hidden;
transition: all 0.3s ease;
}
.accordion-content.active {
padding: 1.5rem;
max-height: 5000px;
}
.accordion-icon {
transition: transform 0.3s ease;
font-size: 0.8em;
}
.accordion-header.active .accordion-icon {
transform: rotate(180deg);
}
.accordion-content p {
margin-bottom: 1rem;
}
.accordion-content p:last-child {
margin-bottom: 0;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,73 @@
// competence.js - Скрипты для страницы компетенций
function openCompetenceModal(imageUrl, title) {
console.log('Opening competence modal with:', imageUrl);
const modal = document.getElementById('competenceModal');
const modalImg = document.getElementById('competenceModalImage');
const modalTitle = document.getElementById('competenceModalTitle');
if (modal && modalImg) {
modal.style.display = "block";
modalImg.src = imageUrl;
if (title && modalTitle) {
modalTitle.textContent = title;
}
// Добавляем класс для анимации
setTimeout(() => {
modal.classList.add('active');
}, 10);
// Подстраиваем размер изображения
adjustCompetenceModalImageSize();
}
}
function closeCompetenceModal() {
const modal = document.getElementById('competenceModal');
if (modal) {
modal.classList.remove('active');
setTimeout(() => {
modal.style.display = "none";
}, 300);
}
}
function adjustCompetenceModalImageSize() {
const modalImg = document.getElementById('competenceModalImage');
if (modalImg) {
const maxWidth = window.innerWidth * 0.9;
const maxHeight = window.innerHeight * 0.8;
modalImg.style.maxWidth = `${maxWidth}px`;
modalImg.style.maxHeight = `${maxHeight}px`;
}
}
// Инициализация после загрузки DOM
document.addEventListener('DOMContentLoaded', function() {
// Закрытие модального окна при клике вне изображения
document.addEventListener('click', function(event) {
const modal = document.getElementById('competenceModal');
if (event.target === modal) {
closeCompetenceModal();
}
});
// Закрытие по ESC
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeCompetenceModal();
}
});
// Адаптация размера изображения при изменении размера окна
window.addEventListener('resize', function() {
const modalImg = document.getElementById('competenceModalImage');
if (modalImg && modalImg.src) {
adjustCompetenceModalImageSize();
}
});
console.log('Competence page scripts initialized');
});

View File

@ -0,0 +1,94 @@
// Mobile Menu Script
document.addEventListener('DOMContentLoaded', function() {
console.log('Mobile menu script loaded'); // Для отладки
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
const mobileMenuClose = document.getElementById('mobileMenuClose');
const mobileMenuOverlay = document.getElementById('mobileMenuOverlay');
const mobileMenu = document.getElementById('mobileMenu');
const mobileThemeToggle = document.getElementById('mobile-theme-toggle');
const mainThemeToggle = document.getElementById('theme-toggle');
// Проверяем, что элементы существуют
if (!mobileMenuBtn || !mobileMenu) {
console.error('Mobile menu elements not found');
return;
}
console.log('Mobile menu elements found:', {
mobileMenuBtn,
mobileMenuClose,
mobileMenuOverlay,
mobileMenu,
mobileThemeToggle,
mainThemeToggle
});
// Открытие мобильного меню
mobileMenuBtn.addEventListener('click', function() {
console.log('Opening mobile menu');
mobileMenu.classList.add('active');
mobileMenuOverlay.style.display = 'block';
document.body.style.overflow = 'hidden';
});
// Закрытие мобильного меню
function closeMobileMenu() {
console.log('Closing mobile menu');
mobileMenu.classList.remove('active');
mobileMenuOverlay.style.display = 'none';
document.body.style.overflow = '';
}
if (mobileMenuClose) {
mobileMenuClose.addEventListener('click', closeMobileMenu);
}
if (mobileMenuOverlay) {
mobileMenuOverlay.addEventListener('click', closeMobileMenu);
}
// Закрытие меню при клике на ссылку
const mobileNavLinks = document.querySelectorAll('.mobile-nav-link');
mobileNavLinks.forEach(link => {
link.addEventListener('click', closeMobileMenu);
});
// Синхронизация переключателей темы
function syncThemeToggles() {
if (mobileThemeToggle && mainThemeToggle) {
mobileThemeToggle.checked = mainThemeToggle.checked;
}
}
if (mainThemeToggle) {
mainThemeToggle.addEventListener('change', function() {
console.log('Main theme toggle changed:', this.checked);
syncThemeToggles();
});
}
if (mobileThemeToggle) {
mobileThemeToggle.addEventListener('change', function() {
console.log('Mobile theme toggle changed:', this.checked);
if (mainThemeToggle) {
mainThemeToggle.checked = this.checked;
// Триггерим событие change
const event = new Event('change');
mainThemeToggle.dispatchEvent(event);
}
});
}
// Инициализация синхронизации
syncThemeToggles();
// Закрытие меню по ESC
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeMobileMenu();
}
});
console.log('Mobile menu script initialized successfully');
});

View File

@ -0,0 +1,74 @@
// recall.js - Скрипты для страницы отзывов
function openModal(imageUrl, title) {
console.log('Opening modal with:', imageUrl);
const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage');
const modalTitle = document.getElementById('modalTitle');
if (modal && modalImg) {
modal.style.display = "block";
modalImg.src = imageUrl;
if (title && modalTitle) {
modalTitle.textContent = title;
}
// Добавляем класс для анимации
setTimeout(() => {
modal.classList.add('active');
}, 10);
// Подстраиваем размер изображения
adjustModalImageSize();
}
}
function closeModal() {
const modal = document.getElementById('imageModal');
if (modal) {
modal.classList.remove('active');
setTimeout(() => {
modal.style.display = "none";
}, 300);
}
}
function adjustModalImageSize() {
const modalImg = document.getElementById('modalImage');
const modalContent = document.querySelector('.modal-content');
if (modalImg && modalContent) {
const maxWidth = window.innerWidth * 0.9;
const maxHeight = window.innerHeight * 0.8;
modalImg.style.maxWidth = `${maxWidth}px`;
modalImg.style.maxHeight = `${maxHeight}px`;
}
}
// Инициализация после загрузки DOM
document.addEventListener('DOMContentLoaded', function() {
// Закрытие модального окна при клике вне изображения
document.addEventListener('click', function(event) {
const modal = document.getElementById('imageModal');
if (event.target === modal) {
closeModal();
}
});
// Закрытие по ESC
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
// Адаптация размера изображения при изменении размера окна
window.addEventListener('resize', function() {
const modalImg = document.getElementById('modalImage');
if (modalImg && modalImg.src) {
adjustModalImageSize();
}
});
console.log('Recall page scripts initialized');
});

View File

@ -0,0 +1,46 @@
// solution-accordion.js
function toggleAccordion(header) {
const content = header.nextElementSibling;
const icon = header.querySelector('.accordion-icon');
// Переключаем только текущий аккордеон
header.classList.toggle('active');
content.classList.toggle('active');
icon.style.transform = header.classList.contains('active') ? 'rotate(180deg)' : 'rotate(0deg)';
}
// Функция для открытия всех аккордеонов
function expandAll() {
document.querySelectorAll('.accordion-content').forEach(content => {
content.classList.add('active');
});
document.querySelectorAll('.accordion-header').forEach(header => {
header.classList.add('active');
});
document.querySelectorAll('.accordion-icon').forEach(icon => {
icon.style.transform = 'rotate(180deg)';
});
}
// Функция для закрытия всех аккордеонов
function collapseAll() {
document.querySelectorAll('.accordion-content').forEach(content => {
content.classList.remove('active');
});
document.querySelectorAll('.accordion-header').forEach(header => {
header.classList.remove('active');
});
document.querySelectorAll('.accordion-icon').forEach(icon => {
icon.style.transform = 'rotate(0deg)';
});
}
// Автоматически открываем первый аккордеон в каждой карточке при загрузке
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.content-card').forEach(card => {
const firstAccordion = card.querySelector('.accordion-header');
if (firstAccordion) {
toggleAccordion(firstAccordion);
}
});
});

View File

@ -0,0 +1,68 @@
// Theme Switcher Script
document.addEventListener('DOMContentLoaded', function() {
const themeToggle = document.getElementById('theme-toggle');
const mobileThemeToggle = document.getElementById('mobile-theme-toggle');
const themeCSS = document.getElementById('theme-css');
// Проверяем сохраненную тему в localStorage
const savedTheme = localStorage.getItem('theme');
// Устанавливаем светлую тему по умолчанию
if (savedTheme === 'dark') {
switchToDarkTheme();
} else {
switchToLightTheme(); // Светлая тема по умолчанию
}
// Обработчик переключения темы для десктопного переключателя
if (themeToggle) {
themeToggle.addEventListener('change', function() {
if (this.checked) {
switchToLightTheme();
} else {
switchToDarkTheme();
}
});
}
// Обработчик переключения темы для мобильного переключателя
if (mobileThemeToggle) {
mobileThemeToggle.addEventListener('change', function() {
if (this.checked) {
switchToLightTheme();
} else {
switchToDarkTheme();
}
// Синхронизируем оба переключателя
if (themeToggle) {
themeToggle.checked = this.checked;
}
});
}
function switchToLightTheme() {
themeCSS.href = themeCSS.href.replace('styles_dark.css', 'styles_w.css');
if (themeToggle) themeToggle.checked = true;
if (mobileThemeToggle) mobileThemeToggle.checked = true;
localStorage.setItem('theme', 'light');
}
function switchToDarkTheme() {
themeCSS.href = themeCSS.href.replace('styles_w.css', 'styles_dark.css');
if (themeToggle) themeToggle.checked = false;
if (mobileThemeToggle) mobileThemeToggle.checked = false;
localStorage.setItem('theme', 'dark');
}
// Синхронизация переключателей при загрузке
if (themeToggle && mobileThemeToggle) {
mobileThemeToggle.checked = themeToggle.checked;
}
// Обработка ошибок загрузки CSS
themeCSS.onerror = function() {
console.error('Ошибка загрузки CSS файла темы');
// Восстанавливаем светлую тему по умолчанию при ошибке
themeCSS.href = themeCSS.href.replace('styles_dark.css', 'styles_w.css');
};
});