diff --git a/hypn0/hypn0_site/models.py b/hypn0/hypn0_site/models.py
index 1a931ae..b795caa 100644
--- a/hypn0/hypn0_site/models.py
+++ b/hypn0/hypn0_site/models.py
@@ -567,3 +567,19 @@ class TbBlogPost(models.Model):
def get_absolute_url(self) -> str:
"""Возвращает канонический URL статьи блога."""
return f"/blog/{self.slug}" if self.slug else f"/blog/{self.pk}"
+
+ @property
+ def blog_nav_title(self) -> str:
+ """Детерминированный выбор названия раздела блога для навигации на основе ID статьи."""
+ nav_titles = (
+ "Блог Мозгоклюя",
+ "Записки Гипножабы",
+ "Дневник Мозговых Перцепторов",
+ "Хроники психо-резонанса",
+ "Мемуары астрального архива",
+ "Манифесты волновой матрицы",
+ "Бортовой журнал транс-погружения",
+ )
+ if not self.id:
+ return nav_titles[0]
+ return nav_titles[self.id % len(nav_titles)]
diff --git a/hypn0/hypn0_site/tests.py b/hypn0/hypn0_site/tests.py
index fa8f221..f6e99a3 100644
--- a/hypn0/hypn0_site/tests.py
+++ b/hypn0/hypn0_site/tests.py
@@ -1386,7 +1386,7 @@ class BlogPostModelAndAdminTests(BaseMediaTestCase):
self.assertContains(resp_parent, "Глава 1: Первичная модуляция")
self.assertContains(resp_parent, "Глава 2: Фазовый сдвиг матрицы")
- # 2. Проверка первой главы (связанный SVG, ссылка на родителя, следующая глава)
+ # 2. Проверка первой главы (связанный SVG, ссылка на родителя, следующая глава, хлебные крошки и JSON-LD)
resp_ch1 = self.client.get(f"/blog/{ch1.slug}")
self.assertEqual(resp_ch1.status_code, 200)
self.assertContains(resp_ch1, "Глава 1: Первичная модуляция")
@@ -1399,6 +1399,18 @@ class BlogPostModelAndAdminTests(BaseMediaTestCase):
self.assertContains(resp_ch1, "Следующая глава цикла")
self.assertContains(resp_ch1, "Глава 2: Фазовый сдвиг матрицы")
+ # Проверка хлебных крошек
+ self.assertContains(resp_ch1, 'aria-label="Хлебные крошки"')
+ self.assertContains(resp_ch1, ch1.blog_nav_title)
+ self.assertContains(resp_ch1, "Глава 1 из 2")
+ self.assertContains(resp_ch1, 'aria-current="page"')
+
+ # Проверка JSON-LD BreadcrumbList
+ self.assertContains(resp_ch1, '"@type": "BreadcrumbList"')
+ self.assertContains(resp_ch1, '"name": "Главная"')
+ self.assertContains(resp_ch1, '"name": "Хроники гипноза"')
+ self.assertContains(resp_ch1, series_parent.s_title)
+
# 3. Проверка второй главы (ссылка назад на главу 1)
resp_ch2 = self.client.get(f"/blog/{ch2.slug}")
self.assertEqual(resp_ch2.status_code, 200)
diff --git a/hypn0/hypn0_site/views.py b/hypn0/hypn0_site/views.py
index d9b8c5b..06dd006 100644
--- a/hypn0/hypn0_site/views.py
+++ b/hypn0/hypn0_site/views.py
@@ -710,6 +710,7 @@ def blog_detail(request: HttpRequest, slug: str) -> HttpResponse:
"svg_stats": svg_stats,
"series_parent": series_parent,
"series_posts": series_posts,
+ "series_total_count": len(series_posts) if series_posts else 0,
"prev_post": prev_post,
"next_post": next_post,
}
diff --git a/hypn0/templates/_base.html b/hypn0/templates/_base.html
index f0bcde3..de7d243 100644
--- a/hypn0/templates/_base.html
+++ b/hypn0/templates/_base.html
@@ -101,13 +101,16 @@ val=>{if(val)document.documentElement.classList.add('dark');else document.docume
файла css/tailwind-custom.css (подключён в input.css через @import).{% endcomment %}
{% endif %}
+ [
+ {
+ "@context": "https://schema.org",
+ "@type": "WebSite",
+ "name": "hypn0.xyz",
+ "url": "{{ request.scheme }}://{{ request.get_host }}/"
+ }{% block EXTRA_LD_JSON %}
+ {% endblock EXTRA_LD_JSON %}
+ ]
+