2026-04-09 22:22:55 +05:00
|
|
|
|
---
|
|
|
|
|
|
import "@styles/global.css";
|
|
|
|
|
|
import { SITE_TITLE_SUFFIX } from "@constants";
|
|
|
|
|
|
|
|
|
|
|
|
import Header from "@components/layout/header/Header.astro";
|
|
|
|
|
|
import Footer from "@components/layout/footer/Footer.astro";
|
|
|
|
|
|
import Breadcrumbs from "@components/base/Breadcrumbs.astro";
|
|
|
|
|
|
import ConsultationModal from "@components/base/ConsultationModal.astro";
|
2026-04-17 17:35:17 +05:00
|
|
|
|
import Toast from "@components/base/Toast.astro";
|
2026-04-09 22:22:55 +05:00
|
|
|
|
import PostSocialShare from "@components/blog/PostSocialShare.astro";
|
|
|
|
|
|
import PostReactionButtons from "@components/blog/PostReactionButtons.astro";
|
2026-04-26 23:47:23 +05:00
|
|
|
|
import PostViewCounter from "@components/blog/PostViewCounter.astro";
|
2026-04-09 22:22:55 +05:00
|
|
|
|
|
|
|
|
|
|
export interface Props {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
canonicalLink?: string;
|
|
|
|
|
|
breadcrumbs?: Array<{ label: string; href?: string }>;
|
|
|
|
|
|
heroImage: string;
|
|
|
|
|
|
heroAlt: string;
|
|
|
|
|
|
category: string;
|
|
|
|
|
|
postTitle: string;
|
|
|
|
|
|
date: string;
|
|
|
|
|
|
author: string;
|
|
|
|
|
|
readTime: string;
|
2026-04-18 18:25:10 +05:00
|
|
|
|
readmeTime: string;
|
2026-04-09 22:22:55 +05:00
|
|
|
|
postId: string;
|
|
|
|
|
|
postUrl: string;
|
|
|
|
|
|
initialLikes?: number;
|
|
|
|
|
|
initialDislikes?: number;
|
2026-04-26 23:47:23 +05:00
|
|
|
|
initialViews?: number;
|
2026-04-09 22:22:55 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
|
title,
|
|
|
|
|
|
description,
|
|
|
|
|
|
canonicalLink,
|
|
|
|
|
|
breadcrumbs,
|
|
|
|
|
|
heroImage,
|
|
|
|
|
|
heroAlt,
|
|
|
|
|
|
category,
|
|
|
|
|
|
postTitle,
|
|
|
|
|
|
date,
|
|
|
|
|
|
author,
|
|
|
|
|
|
readTime,
|
2026-04-18 18:25:10 +05:00
|
|
|
|
readmeTime,
|
2026-04-09 22:22:55 +05:00
|
|
|
|
postId,
|
|
|
|
|
|
postUrl,
|
|
|
|
|
|
initialLikes = 0,
|
2026-04-26 23:47:23 +05:00
|
|
|
|
initialDislikes = 0,
|
|
|
|
|
|
initialViews = 0
|
2026-04-09 22:22:55 +05:00
|
|
|
|
} = Astro.props;
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
<!doctype html>
|
|
|
|
|
|
<html lang="ru">
|
2026-04-10 00:48:20 +05:00
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8" />
|
|
|
|
|
|
<meta name="viewport" content="width=device-width" />
|
|
|
|
|
|
<link rel="icon" type="image/svg+xml" href="/favicons/favicon.svg" />
|
|
|
|
|
|
<link rel="icon" href="/favicons/favicon.ico" />
|
|
|
|
|
|
<title>{title} {SITE_TITLE_SUFFIX}</title>
|
|
|
|
|
|
<meta name="description" content={description} />
|
|
|
|
|
|
{canonicalLink && <link rel="canonical" href={canonicalLink} />}
|
2026-04-14 18:59:12 +05:00
|
|
|
|
<link rel="sitemap" href="/sitemap-index.xml" />
|
2026-04-10 00:48:20 +05:00
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
2026-04-17 17:35:17 +05:00
|
|
|
|
<Toast />
|
2026-04-10 00:48:20 +05:00
|
|
|
|
<Header />
|
|
|
|
|
|
<main class="main-content">
|
|
|
|
|
|
{breadcrumbs && breadcrumbs.length > 0 && (
|
|
|
|
|
|
<div class="breadcrumbs-wrapper">
|
|
|
|
|
|
<div class="site-container">
|
2026-04-09 22:22:55 +05:00
|
|
|
|
<Breadcrumbs items={breadcrumbs} />
|
|
|
|
|
|
</div>
|
2026-04-10 00:48:20 +05:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<!-- HERO SECTION С ЭФФЕКТОМ ПРОЗРЕНИЯ -->
|
|
|
|
|
|
<section class="article-hero">
|
|
|
|
|
|
<div class="article-hero-image">
|
2026-04-15 23:06:28 +05:00
|
|
|
|
<img src={heroImage} alt={heroAlt} loading="eager" width="800" height="400" />
|
2026-04-10 00:48:20 +05:00
|
|
|
|
<div class="article-hero-overlay"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="article-hero-content">
|
|
|
|
|
|
<div class="site-container h-full">
|
|
|
|
|
|
<div class="article-hero-inner">
|
|
|
|
|
|
<div class="article-hero-top">
|
|
|
|
|
|
<div class="category-strip">
|
|
|
|
|
|
<span class="category-text">{category}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<h1 class="article-title">{postTitle}</h1>
|
|
|
|
|
|
</div>
|
2026-04-09 22:22:55 +05:00
|
|
|
|
|
2026-04-10 00:48:20 +05:00
|
|
|
|
<div class="article-hero-bottom">
|
|
|
|
|
|
<div class="article-meta">
|
|
|
|
|
|
<span class="meta-item">
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon"><rect width="18" height="18" x="3" y="4" rx="2" ry="2"></rect><line x1="16" x2="16" y1="2" y2="6"></line><line x1="8" x2="8" y1="2" y2="6"></line><line x1="3" x2="21" y1="10" y2="10"></line></svg>
|
|
|
|
|
|
{date}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="meta-item">
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
|
|
|
|
|
|
<span class="meta-author">{author}</span>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="meta-item">
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
|
2026-04-26 23:56:01 +05:00
|
|
|
|
{readmeTime} мин.
|
2026-04-10 00:48:20 +05:00
|
|
|
|
</span>
|
2026-04-26 23:47:23 +05:00
|
|
|
|
<span class="meta-item">
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="meta-icon"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
|
|
|
|
|
<span class="meta-views" data-post-id={postId}>{initialViews}</span>
|
|
|
|
|
|
</span>
|
2026-04-10 00:48:20 +05:00
|
|
|
|
</div>
|
2026-04-09 22:22:55 +05:00
|
|
|
|
|
2026-04-26 18:09:40 +05:00
|
|
|
|
<div class="article-actions">
|
2026-04-10 00:48:20 +05:00
|
|
|
|
<PostReactionButtons postId={postId} initialLikes={initialLikes} initialDislikes={initialDislikes} />
|
2026-04-26 18:09:40 +05:00
|
|
|
|
<PostSocialShare title={postTitle} url={postUrl} />
|
2026-04-09 22:22:55 +05:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-10 00:48:20 +05:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- BODY SECTION: СЕТКА С ЛИПКИМ САЙДБАРОМ -->
|
|
|
|
|
|
<div class="article-body">
|
|
|
|
|
|
<div class="site-container">
|
|
|
|
|
|
<div class="article-layout-grid">
|
|
|
|
|
|
<div class="article-main">
|
2026-04-26 18:09:40 +05:00
|
|
|
|
<slot />
|
2026-04-09 22:22:55 +05:00
|
|
|
|
</div>
|
2026-04-10 00:48:20 +05:00
|
|
|
|
|
|
|
|
|
|
<aside class="article-sidebar" id="sticky-sidebar">
|
|
|
|
|
|
<slot name="sidebar" />
|
|
|
|
|
|
</aside>
|
2026-04-09 22:22:55 +05:00
|
|
|
|
</div>
|
2026-04-10 00:48:20 +05:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
<Footer />
|
2026-04-09 22:22:55 +05:00
|
|
|
|
|
2026-04-26 18:09:40 +05:00
|
|
|
|
<style>
|
2026-04-10 00:48:20 +05:00
|
|
|
|
/* ЧЕРНАЯ НУМЕРАЦИЯ */
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.article-content-wrapper { counter-reset: post-h2; }
|
|
|
|
|
|
.article-content-wrapper .post-content h2 { counter-reset: post-h3; counter-increment: post-h2; display: flex; align-items: baseline; gap: 0.6rem; color: #1e3050; font-weight: 800; }
|
|
|
|
|
|
.article-content-wrapper .post-content h2::before { content: counter(post-h2) "."; color: #1e3050 !important; flex-shrink: 0; }
|
|
|
|
|
|
.article-content-wrapper .post-content h3 { counter-increment: post-h3; display: flex; align-items: baseline; gap: 0.5rem; color: #1e3050; font-weight: 700; }
|
|
|
|
|
|
.article-content-wrapper .post-content h3::before { content: counter(post-h2) "." counter(post-h3) "."; color: #1e3050 !important; font-size: 0.9em; flex-shrink: 0; }
|
2026-04-10 00:48:20 +05:00
|
|
|
|
|
|
|
|
|
|
/* Исключаем заголовок "Читайте также" из нумерации */
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.article-content-wrapper .related-posts .section-title {
|
2026-04-10 00:48:20 +05:00
|
|
|
|
counter-increment: none !important;
|
|
|
|
|
|
display: block !important;
|
2026-04-09 22:22:55 +05:00
|
|
|
|
}
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.article-content-wrapper .related-posts .section-title::before {
|
2026-04-10 00:48:20 +05:00
|
|
|
|
content: none !important;
|
2026-04-09 22:22:55 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-18 18:25:10 +05:00
|
|
|
|
/* Исключаем секцию комментариев из нумерации */
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.article-content-wrapper .comments-wrapper h2,
|
|
|
|
|
|
.article-content-wrapper .comments-wrapper h2::before {
|
2026-04-18 18:25:10 +05:00
|
|
|
|
counter-increment: none !important;
|
|
|
|
|
|
content: none !important;
|
|
|
|
|
|
}
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.article-content-wrapper .comments-wrapper h3,
|
|
|
|
|
|
.article-content-wrapper .comments-wrapper h3::before {
|
2026-04-18 18:25:10 +05:00
|
|
|
|
counter-increment: none !important;
|
|
|
|
|
|
content: none !important;
|
|
|
|
|
|
}
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.article-content-wrapper .comments-wrapper .section-title,
|
|
|
|
|
|
.article-content-wrapper .comments-wrapper .section-title::before {
|
2026-04-18 18:25:10 +05:00
|
|
|
|
counter-increment: none !important;
|
|
|
|
|
|
content: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-10 00:48:20 +05:00
|
|
|
|
/* СКРОЛЛ-ФИКС */
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.article-content-wrapper h2, .article-content-wrapper h3 { scroll-margin-top: 125px; }
|
2026-04-09 22:22:55 +05:00
|
|
|
|
|
2026-04-10 00:48:20 +05:00
|
|
|
|
/* СТИЛЬ АКТИВНОГО ПУНКТА В TOC */
|
|
|
|
|
|
.toc-item.active .toc-link {
|
|
|
|
|
|
color: #eac26e !important;
|
2026-04-09 22:22:55 +05:00
|
|
|
|
font-weight: 700;
|
2026-04-10 00:48:20 +05:00
|
|
|
|
background: rgba(234, 194, 110, 0.1);
|
2026-04-09 22:22:55 +05:00
|
|
|
|
}
|
2026-04-10 00:48:20 +05:00
|
|
|
|
.toc-item.active .toc-link::before { color: #eac26e !important; }
|
2026-04-09 22:22:55 +05:00
|
|
|
|
|
2026-04-10 00:48:20 +05:00
|
|
|
|
html, body { overflow-x: clip !important; }
|
|
|
|
|
|
</style>
|
2026-04-09 22:22:55 +05:00
|
|
|
|
|
2026-04-10 00:48:20 +05:00
|
|
|
|
<style>
|
|
|
|
|
|
.h-full { height: 100%; }
|
|
|
|
|
|
.main-content { padding-top: 0; overflow: visible; }
|
|
|
|
|
|
.breadcrumbs-wrapper { padding-top: 5.5rem; background: #f8fafc; }
|
|
|
|
|
|
|
|
|
|
|
|
/* HERO SECTION */
|
|
|
|
|
|
.article-hero { position: relative; width: 100%; height: 550px; background: #0a1a2e; overflow: hidden; }
|
|
|
|
|
|
.article-hero-image { position: absolute; inset: 0; z-index: 1; }
|
|
|
|
|
|
.article-hero-image img { width: 100%; height: 100%; object-fit: cover; filter: brightness(0.35) grayscale(0.2); transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1); transform: scale(1.04); }
|
|
|
|
|
|
.article-hero-overlay { position: absolute; inset: 0; background: linear-gradient(180deg, transparent 0%, rgba(10, 26, 46, 0.9) 100%); z-index: 2; transition: opacity 0.7s ease; }
|
|
|
|
|
|
|
|
|
|
|
|
.article-hero:hover .article-hero-image img { filter: brightness(1) grayscale(0); transform: scale(1); }
|
|
|
|
|
|
.article-hero:hover .article-hero-overlay { opacity: 0.4; }
|
|
|
|
|
|
|
|
|
|
|
|
.article-hero-content { position: relative; z-index: 10; height: 100%; }
|
|
|
|
|
|
.article-hero-inner { display: flex; flex-direction: column; height: 100%; padding: 3rem 0; }
|
|
|
|
|
|
.article-hero-top { margin-bottom: 2rem; }
|
|
|
|
|
|
|
|
|
|
|
|
.category-strip { background: #eac26e; padding: 0.5rem 1.5rem; display: inline-block; border-radius: 4px; margin-bottom: 2rem; }
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.category-text { color: #0a1a2e; font-weight: 800; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 1px; font-family: 'Merriweather', Georgia, serif; }
|
|
|
|
|
|
.article-title { color: #fff; font-size: clamp(2rem, 5vw, 3.5rem); font-weight: 900; line-height: 1.15; max-width: 950px; text-shadow: 0 4px 15px rgba(0,0,0,0.4); font-family: 'Merriweather', Georgia, serif; }
|
2026-04-10 00:48:20 +05:00
|
|
|
|
|
|
|
|
|
|
.article-hero-bottom { margin-top: auto; display: flex; justify-content: space-between; align-items: center; padding-top: 2rem; border-top: 1px solid rgba(255, 255, 255, 0.15); }
|
2026-04-10 00:57:54 +05:00
|
|
|
|
.article-actions { display: flex; align-items: center; gap: 2rem; }
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.article-meta { display: flex; gap: 2rem; justify-content: flex-start; width: auto; }
|
|
|
|
|
|
.meta-item { display: flex; align-items: center; gap: 0.6rem; color: #fff; font-size: 0.95rem; white-space: nowrap; }
|
2026-04-10 00:48:20 +05:00
|
|
|
|
.meta-icon { width: 1.1rem; height: 1.1rem; color: #eac26e; }
|
|
|
|
|
|
.meta-author { color: #eac26e; font-weight: 700; }
|
|
|
|
|
|
|
|
|
|
|
|
/* GRID & STICKY SIDEBAR */
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.article-body { padding: 0 0 4rem; background: #f8fafc; overflow: visible; }
|
2026-04-10 00:48:20 +05:00
|
|
|
|
.article-layout-grid { display: flex; gap: 3.5rem; align-items: flex-start; }
|
|
|
|
|
|
.article-main { flex: 1; min-width: 0; }
|
|
|
|
|
|
.article-content-wrapper { background: #fff; padding: 1.5rem 2.5rem 2.5rem; border-radius: 2rem; box-shadow: 0 4px 30px rgba(0,0,0,0.02); }
|
|
|
|
|
|
|
|
|
|
|
|
.article-sidebar { width: 320px; flex-shrink: 0; position: sticky; top: 110px; z-index: 50; }
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1200px) {
|
|
|
|
|
|
.article-sidebar { display: none; }
|
|
|
|
|
|
.article-layout-grid { flex-direction: column; }
|
2026-04-09 22:22:55 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-26 18:09:40 +05:00
|
|
|
|
@media (max-width: 1024px) {
|
|
|
|
|
|
.article-hero-bottom { flex-direction: column; align-items: flex-start; gap: 1.5rem; }
|
|
|
|
|
|
.article-actions { width: 100%; justify-content: flex-start; }
|
|
|
|
|
|
.article-meta { gap: 1.5rem; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-09 22:22:55 +05:00
|
|
|
|
@media (max-width: 768px) {
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.article-hero { height: auto; min-height: 385px; }
|
|
|
|
|
|
.article-title { font-size: 1.5rem; }
|
2026-04-10 00:48:20 +05:00
|
|
|
|
.article-hero-bottom { flex-direction: column; align-items: flex-start; gap: 2rem; }
|
2026-04-26 18:09:40 +05:00
|
|
|
|
.article-actions { width: 100%; justify-content: flex-start; align-items: flex-end; gap: 1.5rem; }
|
|
|
|
|
|
.share-wrapper { display: flex; align-items: flex-end; }
|
|
|
|
|
|
.article-content-wrapper { padding: 2.5rem 1.5rem; padding-top: 0; }
|
|
|
|
|
|
.article-hero-inner { padding: 2rem 0; }
|
|
|
|
|
|
.meta-item { font-size: 0.85rem; }
|
|
|
|
|
|
.article-meta { gap: 0.75rem; flex-wrap: wrap; }
|
|
|
|
|
|
.category-strip { margin-bottom: 1.5rem; padding: 0.4rem 1rem; }
|
|
|
|
|
|
.category-text { font-size: 0.7rem; }
|
2026-04-09 22:22:55 +05:00
|
|
|
|
}
|
2026-04-26 18:09:40 +05:00
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
function setupArticleLogic() {
|
|
|
|
|
|
const sidebar = document.getElementById('sticky-sidebar');
|
|
|
|
|
|
const content = document.getElementById('post-content');
|
|
|
|
|
|
if (!sidebar || !content) return;
|
|
|
|
|
|
|
|
|
|
|
|
const tocLinks = sidebar.querySelectorAll<HTMLAnchorElement>('.toc-link');
|
|
|
|
|
|
const tocItems = sidebar.querySelectorAll('.toc-item');
|
|
|
|
|
|
const articleHeadings = content.querySelectorAll('h2, h3');
|
|
|
|
|
|
const HEADER_OFFSET = 120;
|
|
|
|
|
|
|
|
|
|
|
|
// 1. АВТОПРИВЯЗКА ID К ЗАГОЛОВКАМ (Решает проблему heading-0 не найден)
|
|
|
|
|
|
tocLinks.forEach((link, index) => {
|
|
|
|
|
|
const id = link.getAttribute('href')?.slice(1);
|
|
|
|
|
|
const heading = articleHeadings[index];
|
|
|
|
|
|
if (id && heading) {
|
|
|
|
|
|
heading.id = id;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 2. ПЛАВНЫЙ СКРОЛЛ
|
|
|
|
|
|
tocLinks.forEach(link => {
|
|
|
|
|
|
link.addEventListener('click', (e) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
const id = link.getAttribute('href')?.slice(1);
|
|
|
|
|
|
const target = id ? document.getElementById(id) : null;
|
|
|
|
|
|
if (target) {
|
|
|
|
|
|
const targetPos = target.getBoundingClientRect().top + window.pageYOffset - HEADER_OFFSET;
|
|
|
|
|
|
window.scrollTo({ top: targetPos, behavior: 'smooth' });
|
|
|
|
|
|
history.pushState(null, '', `#${id}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 3. SCROLL SPY (ПОДСВЕТКА)
|
|
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
|
|
|
|
entries.forEach(entry => {
|
|
|
|
|
|
if (entry.isIntersecting) {
|
|
|
|
|
|
const activeId = entry.target.id;
|
|
|
|
|
|
tocItems.forEach(item => {
|
|
|
|
|
|
const match = item.getAttribute('data-toc-target') === activeId;
|
|
|
|
|
|
item.classList.toggle('active', match);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}, {
|
|
|
|
|
|
root: null,
|
|
|
|
|
|
rootMargin: `-${HEADER_OFFSET}px 0px -70% 0px`,
|
|
|
|
|
|
threshold: 0
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
articleHeadings.forEach(h => observer.observe(h));
|
|
|
|
|
|
|
|
|
|
|
|
// 4. КНОПКА КОНСУЛЬТАЦИИ
|
|
|
|
|
|
const consultationBtn = document.getElementById("consultation-btn");
|
|
|
|
|
|
consultationBtn?.addEventListener("click", () => {
|
|
|
|
|
|
window.dispatchEvent(new CustomEvent("open-modal", { detail: "consultation-modal" }));
|
|
|
|
|
|
});
|
2026-04-26 23:47:23 +05:00
|
|
|
|
|
|
|
|
|
|
// 5. СЧЁТЧИК ПРОСМОТРОВ
|
|
|
|
|
|
const viewsEl = document.querySelector('.meta-views') as HTMLElement & { dataset: { postId: string } };
|
|
|
|
|
|
if (viewsEl?.dataset?.postId) {
|
|
|
|
|
|
const postId = viewsEl.dataset.postId;
|
|
|
|
|
|
|
2026-04-27 21:00:34 +05:00
|
|
|
|
fetch(`/api/increment-views?postId=${postId}`)
|
2026-04-27 01:04:06 +05:00
|
|
|
|
.then(res => res.json())
|
|
|
|
|
|
.then(data => {
|
|
|
|
|
|
if (data.views !== undefined) {
|
|
|
|
|
|
viewsEl.textContent = formatViews(data.views);
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {});
|
2026-04-26 23:47:23 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatViews(n: number): string {
|
|
|
|
|
|
if (n >= 1000) {
|
|
|
|
|
|
return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
|
|
|
|
|
}
|
|
|
|
|
|
return n.toString();
|
|
|
|
|
|
}
|
2026-04-26 18:09:40 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", setupArticleLogic);
|
|
|
|
|
|
document.addEventListener("astro:page-load", setupArticleLogic);
|
|
|
|
|
|
</script>
|