Compare commits
12 Commits
490ad60f7c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71d32defbc | ||
|
|
7a7df39c1b | ||
|
|
795153b26e | ||
|
|
8f9e67ec08 | ||
|
|
a8821bcada | ||
|
|
944069f48d | ||
|
|
ed96b01985 | ||
|
|
283ef8ff95 | ||
|
|
952dfda73c | ||
|
|
636bc47155 | ||
|
|
27fc54733e | ||
|
|
fc74e274e5 |
@@ -1,8 +1,11 @@
|
|||||||
---
|
---
|
||||||
import { getLatestColonPost } from '@lib/api/colon-posts';
|
|
||||||
import Author from '@components/AuthorDisplay.astro';
|
import Author from '@components/AuthorDisplay.astro';
|
||||||
|
|
||||||
const colonPost = await getLatestColonPost();
|
const {
|
||||||
|
colonPost=[]
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
{colonPost && (
|
{colonPost && (
|
||||||
@@ -42,150 +45,4 @@ const colonPost = await getLatestColonPost();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Основная карточка */
|
|
||||||
.colon-post-card {
|
|
||||||
background: #ececec;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 800px; /* опционально, для демо */
|
|
||||||
height: 200px; /* фиксированная высота карточки */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colon-post-card:hover {
|
|
||||||
transform: translateY(-4px);
|
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Flex-контейнер: две части */
|
|
||||||
.split-flex {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ЛЕВЫЙ БЛОК: ровно 30% */
|
|
||||||
.left-photo {
|
|
||||||
flex: 0 0 34%; /* ширина 30%, не растягивается */
|
|
||||||
height: 100%;
|
|
||||||
background: #d4d4d4; /* фон, если нет фото */
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.photo-link {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.photo-img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover; /* заполняет контейнер, сохраняя пропорции и обрезаясь */
|
|
||||||
display: block;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.photo-img:hover {
|
|
||||||
transform: scale(1.05); /* легкий эффект при наведении */
|
|
||||||
}
|
|
||||||
|
|
||||||
.photo-placeholder {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ПРАВЫЙ БЛОК: 70% */
|
|
||||||
.right-content {
|
|
||||||
flex: 1; /* занимает оставшееся место (70%) */
|
|
||||||
height: 100%;
|
|
||||||
padding: 16px 20px; /* внутренние отступы */
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Обёртка для контента, чтобы занять всю высоту и распределить пространство */
|
|
||||||
.content-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Заголовок жирным */
|
|
||||||
.bold-title {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.4;
|
|
||||||
color: #2c3e50;
|
|
||||||
margin: 0 0 8px 0;
|
|
||||||
transition: color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-link {
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-link:hover .bold-title {
|
|
||||||
color: #3498db;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Мета-строка: прижимаем к низу */
|
|
||||||
.meta-line {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #666;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-top: auto; /* это прижимает мету к низу */
|
|
||||||
}
|
|
||||||
|
|
||||||
.separator {
|
|
||||||
color: #aaa;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author :global(a) {
|
|
||||||
color: #2c3e50;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author :global(a:hover) {
|
|
||||||
color: #3498db;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Адаптивность */
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.colon-post-card {
|
|
||||||
height: auto;
|
|
||||||
min-height: 180px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-photo {
|
|
||||||
flex: 0 0 30%;
|
|
||||||
aspect-ratio: 1 / 1; /* сохраняем квадрат на мобильных */
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-content {
|
|
||||||
padding: 12px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bold-title {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Если нужно точное соответствие макету, можно добавить медиа-запросы под свои нужды */
|
|
||||||
</style>
|
|
||||||
19
src/components/Content/MoreArticles.astro
Normal file
19
src/components/Content/MoreArticles.astro
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
import { getLatestPosts } from '@api/posts.js';
|
||||||
|
import ContentGrid from '@components/ContentGrid.astro';
|
||||||
|
|
||||||
|
const { posts, pageInfo } = await getLatestPosts(11);
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<ContentGrid
|
||||||
|
items={posts}
|
||||||
|
pageInfo={pageInfo}
|
||||||
|
type="latest"
|
||||||
|
perLoad={11}
|
||||||
|
showCount={false}
|
||||||
|
gridColumns="3"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
41
src/components/Content/RelatedPosts.astro
Normal file
41
src/components/Content/RelatedPosts.astro
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
// RelatedPosts.astro
|
||||||
|
import { getRelatedPosts } from '@api/posts.js';
|
||||||
|
|
||||||
|
const relatedPosts = await getRelatedPosts();
|
||||||
|
const hasPosts = relatedPosts && relatedPosts.length > 0;
|
||||||
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
hasPosts && (
|
||||||
|
<section class="related-posts">
|
||||||
|
<div class="related-container">
|
||||||
|
<div class="related-posts__header">
|
||||||
|
<h2 class="related-posts__title">САМОЕ ЧИТАЕМОЕ</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="related-posts__flex">
|
||||||
|
{relatedPosts.map((post) => (
|
||||||
|
<a href={post.url} class="related-post">
|
||||||
|
{post.image ? (
|
||||||
|
<div class="related-post__image-wrapper">
|
||||||
|
<img
|
||||||
|
src={post.image}
|
||||||
|
alt={post.imageAlt}
|
||||||
|
class="related-post__image"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div class="related-post__image-wrapper related-post__image-wrapper--placeholder">
|
||||||
|
<span>Нет изображения</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<h3 class="related-post__title">{post.title}</h3>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
import FooterMenu from '@components/FooterMenu.astro';
|
import FooterMenu from '@components/Menus/FooterMenu.astro';
|
||||||
|
import CookieConsent from '@components/Integrations/CookieConsent.astro';
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -105,7 +106,6 @@ const footerId = `footer-profile`;
|
|||||||
<div class="footer__expanded-bottom">
|
<div class="footer__expanded-bottom">
|
||||||
<button
|
<button
|
||||||
class="footer__collapse-btn"
|
class="footer__collapse-btn"
|
||||||
onclick="toggleFooter(document.querySelector('#${footerId} .footer__toggle'))"
|
|
||||||
aria-label="Свернуть футер"
|
aria-label="Свернуть футер"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -126,246 +126,9 @@ const footerId = `footer-profile`;
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<CookieConsent />
|
||||||
|
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<style is:global>
|
|
||||||
.footer {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: #303030;
|
|
||||||
border-top: 2px solid #404040;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
z-index: 100;
|
|
||||||
box-shadow: 0 -2px 15px rgba(0, 0, 0, 0.3);
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Свернутое состояние */
|
|
||||||
.footer__collapsed {
|
|
||||||
padding: 12px 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1200px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 8px 16px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__toggle:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__publication-name {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #ffffff;
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
margin: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__arrow {
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
color: #cccccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* При развернутом состоянии стрелка поворачивается на 180° (вниз) */
|
|
||||||
.footer__toggle[aria-expanded="true"] .footer__arrow {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__copyright-collapsed {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #cccccc;
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
margin: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Раскрытое состояние */
|
|
||||||
.footer__expanded {
|
|
||||||
max-height: 0;
|
|
||||||
opacity: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
padding: 0 20px;
|
|
||||||
background: #303030;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__expanded:not([hidden]) {
|
|
||||||
max-height: 400px;
|
|
||||||
opacity: 1;
|
|
||||||
padding: 20px;
|
|
||||||
border-top: 1px solid #404040;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__menu {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__menu-list {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 20px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__menu-item {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__menu-link {
|
|
||||||
color: #ffffff;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__menu-link:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.15);
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__copyright-expanded {
|
|
||||||
color: #cccccc;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
max-width: 800px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__copyright-expanded p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__expanded-bottom {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__collapse-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #ffffff;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__collapse-btn:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-conf-docs{
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__age {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
border: 2px solid #404040;
|
|
||||||
margin-left: 10px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Адаптивность */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.footer__toggle {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__publication-name,
|
|
||||||
.footer__copyright-collapsed {
|
|
||||||
margin: 4px 0;
|
|
||||||
text-align: center;
|
|
||||||
flex: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__menu-list {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__copyright-expanded {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Анимация для стрелки */
|
|
||||||
@keyframes bounce {
|
|
||||||
0%, 100% {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__toggle:hover .footer__arrow {
|
|
||||||
animation: bounce 0.5s ease;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script is:inline>
|
|
||||||
function toggleFooter(button) {
|
|
||||||
const footer = button.closest('.footer');
|
|
||||||
const expandedSection = footer.querySelector('.footer__expanded');
|
|
||||||
const isExpanded = button.getAttribute('aria-expanded') === 'true';
|
|
||||||
|
|
||||||
// Обновляем состояние кнопки
|
|
||||||
button.setAttribute('aria-expanded', !isExpanded);
|
|
||||||
button.setAttribute('aria-label', isExpanded ? 'Развернуть футер' : 'Свернуть футер');
|
|
||||||
|
|
||||||
// Показываем/скрываем контент
|
|
||||||
if (isExpanded) {
|
|
||||||
expandedSection.setAttribute('hidden', '');
|
|
||||||
} else {
|
|
||||||
expandedSection.removeAttribute('hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -2,13 +2,14 @@
|
|||||||
const { category, contentType } = Astro.props;
|
const { category, contentType } = Astro.props;
|
||||||
|
|
||||||
import Stores from './LazyStores.astro';
|
import Stores from './LazyStores.astro';
|
||||||
import MainMenu from '@components/MainMenu.astro';
|
import MainMenu from '@components/Menus/MainMenu.astro';
|
||||||
|
|
||||||
const MENU_ID = 3340; // 103246 (бургер 1). 103247 (бургер 2 )
|
const MENU_ID = 3340; // 103246 (бургер 1). 103247 (бургер 2 )
|
||||||
let menuItems = [];
|
let menuItems = [];
|
||||||
---
|
---
|
||||||
|
|
||||||
<header class="header" itemscope itemtype="https://schema.org/WPHeader">
|
<header class="header" itemscope itemtype="https://schema.org/WPHeader">
|
||||||
|
|
||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<a href="/"><img alt="Профиль" width="249" height="55" src="https://cdn.profile.ru/wp-content/themes/profile/assets/img/profile-logo-delovoy.svg"></a>
|
<a href="/"><img alt="Профиль" width="249" height="55" src="https://cdn.profile.ru/wp-content/themes/profile/assets/img/profile-logo-delovoy.svg"></a>
|
||||||
|
|
||||||
@@ -25,58 +26,6 @@ let menuItems = [];
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MainMenu menuId={MENU_ID} />
|
<MainMenu menuId={MENU_ID} />
|
||||||
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<style>
|
|
||||||
.top-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__subtitle {
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-left: 42px;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__subtitle::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: -15px;
|
|
||||||
width: 3px;
|
|
||||||
height: 80%;
|
|
||||||
border-left: 3px solid;
|
|
||||||
transform: translate(0, -40%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__news-badge {
|
|
||||||
color: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
position: relative;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__news-badge::after {
|
|
||||||
content: '|';
|
|
||||||
position: absolute;
|
|
||||||
right: -2px;
|
|
||||||
color: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__subtitle a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__subtitle a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
6
src/components/Integrations/CookieConsent.astro
Normal file
6
src/components/Integrations/CookieConsent.astro
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<div id="cookie-consent-banner" class="cookie-consent">
|
||||||
|
<div class="cookie-consent-content">
|
||||||
|
<div class="cookie-consent-text">Мы используем файлы cookie для улучшения работы сайта. Продолжая использование сайта, вы соглашаетесь с <a style="color: white; text-decoration: underline;" href="https://profile.ru/zashita-personalnyh-dannyh/" target="_blank">условиями</a></div>
|
||||||
|
<button id="cookie-consent-accept" class="cookie-consent-button">Принять</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,128 +1,26 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
// src/components/MainLine.astro
|
// src/components/MainLine.astro
|
||||||
import EndnewsList from '@components/EndnewsList.astro';
|
import EndnewsList from '@components/EndnewsList.astro';
|
||||||
import MainPostWidget from '@components/MainPostWidget.astro';
|
import MainPostWidget from '@components/MainPostWidget.astro';
|
||||||
import ColonPost from '@components/ColonPost.astro';
|
import ColonPost from '@components/ColonPost.astro';
|
||||||
|
|
||||||
|
const {
|
||||||
|
mainPost=[],
|
||||||
|
colonPost=[]
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="three-col-block">
|
<div class="three-col-block">
|
||||||
<div class="left-col"><EndnewsList /></div>
|
<div class="left-col"><EndnewsList /></div>
|
||||||
<div class="center-col">
|
<div class="center-col">
|
||||||
<div class="center-top"><MainPostWidget /></div>
|
<div class="center-top">
|
||||||
<div class="center-bottom"><ColonPost /></div>
|
<MainPostWidget mainPost={mainPost}/>
|
||||||
|
</div>
|
||||||
|
<div class="center-bottom">
|
||||||
|
<ColonPost colonPost={colonPost}/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-col">Правая колонка (260px)</div>
|
<div class="right-col">Правая колонка (260px)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Основной контейнер */
|
|
||||||
.three-col-block {
|
|
||||||
display: flex;
|
|
||||||
margin: 30px 0;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
align-items: stretch; /* ВАЖНО: растягиваем все колонки на всю высоту */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ЛЕВАЯ КОЛОНКА - фиксированная */
|
|
||||||
.left-col {
|
|
||||||
flex: 0 0 260px;
|
|
||||||
min-width: 260px;
|
|
||||||
max-width: 260px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex; /* Добавляем flex для растягивания содержимого */
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-col > :deep(*) {
|
|
||||||
width: 100%; /* Растягиваем компонент на всю ширину */
|
|
||||||
height: 100%; /* Растягиваем компонент на всю высоту */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ЦЕНТРАЛЬНАЯ КОЛОНКА - гибкая */
|
|
||||||
.center-col {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
margin: 0 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-top, .center-bottom {
|
|
||||||
#flex: 1 1 0; /* ВАЖНО: оба блока занимают равную высоту */
|
|
||||||
background: #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: hidden;
|
|
||||||
word-wrap: break-word;
|
|
||||||
display: flex; /* Для растягивания внутреннего содержимого */
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-top > :deep(*), .center-bottom > :deep(*) {
|
|
||||||
width: 100%;
|
|
||||||
flex: 1; /* Растягиваем компонент на всю доступную высоту */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ПРАВАЯ КОЛОНКА - фиксированная */
|
|
||||||
.right-col {
|
|
||||||
flex: 0 0 260px;
|
|
||||||
min-width: 260px;
|
|
||||||
max-width: 260px;
|
|
||||||
background: #f0f0f0;
|
|
||||||
padding: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex; /* Добавляем flex для растягивания содержимого */
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-col > :deep(*) {
|
|
||||||
width: 100%;
|
|
||||||
flex: 1; /* Растягиваем контент на всю высоту */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ограничиваем контент внутри центральной колонки */
|
|
||||||
.center-col > * {
|
|
||||||
max-width: 100%;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* МОБИЛЬНАЯ ВЕРСИЯ */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.three-col-block {
|
|
||||||
flex-direction: column;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
max-width: 100%;
|
|
||||||
overflow: visible;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-col, .center-col, .right-col {
|
|
||||||
flex: 1 1 100%;
|
|
||||||
min-width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-col {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-col{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Сбрасываем flex свойства для мобильной версии */
|
|
||||||
.left-col, .right-col {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-top, .center-bottom {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
// src/components/MainPostWidget.astro
|
|
||||||
import { getLatestMainPost } from '@lib/api/main-posts';
|
|
||||||
|
|
||||||
import Author from '@components/AuthorDisplay.astro';
|
import Author from '@components/AuthorDisplay.astro';
|
||||||
import CategoryBadge from '@components/CategoryBadge.astro'; // цветная плитка рубрик
|
import CategoryBadge from '@components/CategoryBadge.astro'; // цветная плитка рубрик
|
||||||
|
|
||||||
const mainPost = await getLatestMainPost();
|
const {
|
||||||
|
mainPost=[]
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
if (!mainPost) return null;
|
if (!mainPost) return null;
|
||||||
|
|
||||||
@@ -88,115 +88,3 @@ const formattedDate = postDate.toLocaleDateString('ru-RU', {
|
|||||||
{categoryName && <meta itemprop="articleSection" content={categoryName} />}
|
{categoryName && <meta itemprop="articleSection" content={categoryName} />}
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<style>
|
|
||||||
/* ОСНОВНОЕ: ограничиваем ширину виджета */
|
|
||||||
.main-post-widget {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: white;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
aspect-ratio: 16/9;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-link {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-link:hover .post-image {
|
|
||||||
transform: scale(1.03);
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-placeholder {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(135deg, #f5f5f5, #e0e0e0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-badge {
|
|
||||||
position: absolute;
|
|
||||||
top: 16px;
|
|
||||||
left: 16px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.625rem;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
color: white;
|
|
||||||
z-index: 2;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-overlay {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 24px;
|
|
||||||
background: linear-gradient(transparent, rgba(0,0,0,0.7) 70%);
|
|
||||||
color: white;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-overlay {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
opacity: 0.9;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-overlay {
|
|
||||||
margin: 0 0 12px 0;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-link {
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-link:hover {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-overlay {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Адаптивность */
|
|
||||||
@media (max-width: 1023px) {
|
|
||||||
.main-post-widget {
|
|
||||||
border-radius: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-overlay {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-overlay {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
// BurgerMenu.astro
|
// BurgerMenu.astro
|
||||||
import { fetchMenu } from '@api/menu';
|
import { fetchMenu } from '@api/menu';
|
||||||
|
import { normalizeMenuUrl } from '@utils/url';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
colorMenuId: number;
|
colorMenuId: number;
|
||||||
@@ -30,7 +31,7 @@ const submenu = await fetchMenu({ id: submenuId });
|
|||||||
return (
|
return (
|
||||||
<li class="burger-menu__item" key={item.id}>
|
<li class="burger-menu__item" key={item.id}>
|
||||||
<a
|
<a
|
||||||
href={item.url}
|
href={normalizeMenuUrl(item.url)}
|
||||||
class={`burger-menu__link burger-menu__link--colored ${colorClass}`}
|
class={`burger-menu__link burger-menu__link--colored ${colorClass}`}
|
||||||
target={item.target || '_self'}
|
target={item.target || '_self'}
|
||||||
>
|
>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { fetchMenu } from '@api/menu';
|
import { fetchMenu } from '@api/menu';
|
||||||
|
import { normalizeMenuUrl } from '@utils/url';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
menuId: number;
|
menuId: number;
|
||||||
@@ -20,7 +21,7 @@ if (!menu) {
|
|||||||
{menu.menuItems.nodes.map(item => (
|
{menu.menuItems.nodes.map(item => (
|
||||||
<li class="simple-menu-item" key={item.id}>
|
<li class="simple-menu-item" key={item.id}>
|
||||||
<a
|
<a
|
||||||
href={item.url}
|
href={normalizeMenuUrl(item.url)}
|
||||||
class="simple-menu-link"
|
class="simple-menu-link"
|
||||||
target={item.target || '_self'}
|
target={item.target || '_self'}
|
||||||
>
|
>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { fetchMenu } from '@api/menu';
|
import { fetchMenu } from '@api/menu';
|
||||||
import BurgerMenu from '@components/BurgerMenu.astro';
|
import BurgerMenu from '@components/Menus/BurgerMenu.astro';
|
||||||
|
import { normalizeMenuUrl } from '@utils/url';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
menuId: number;
|
menuId: number;
|
||||||
@@ -41,7 +42,7 @@ if (!menu) {
|
|||||||
return (
|
return (
|
||||||
<li class="primary-nav__item" key={item.id}>
|
<li class="primary-nav__item" key={item.id}>
|
||||||
<a
|
<a
|
||||||
href={item.url}
|
href={normalizeMenuUrl(item.url)}
|
||||||
class={`primary-nav__link ${colorClass}`}
|
class={`primary-nav__link ${colorClass}`}
|
||||||
target={item.target || '_self'}
|
target={item.target || '_self'}
|
||||||
>
|
>
|
||||||
@@ -57,151 +58,6 @@ if (!menu) {
|
|||||||
|
|
||||||
<BurgerMenu colorMenuId={103246} standardMenuId={103247} submenuId={3341} />
|
<BurgerMenu colorMenuId={103246} standardMenuId={103247} submenuId={3341} />
|
||||||
|
|
||||||
<style>
|
|
||||||
.primary-nav {
|
|
||||||
margin: 12px 0;
|
|
||||||
border-top: 1px solid black;
|
|
||||||
border-bottom: 1px solid black;
|
|
||||||
background-color: white;
|
|
||||||
width: 100%;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Стили только для десктопа */
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.primary-nav.fixed {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin: 0;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Добавляем отступ для контента, когда меню фиксированное */
|
|
||||||
.primary-nav.fixed + * {
|
|
||||||
margin-top: 60px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Обертка для центрирования контента */
|
|
||||||
.primary-nav__wrapper {
|
|
||||||
width: 100%;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* При фиксированном меню обертка тоже фиксируется */
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.primary-nav.fixed .primary-nav__wrapper {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 20px; /* Добавляем отступы по бокам */
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-nav__content {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
min-height: 48px;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Для десктопа добавляем ограничение ширины */
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.primary-nav__content {
|
|
||||||
max-width: 1200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Стили для логотипа, появляющегося при скролле */
|
|
||||||
.primary-nav__logo-scroll {
|
|
||||||
display: none;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
margin-right: 20px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Показываем логотип только когда меню фиксированное и на десктопе */
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.primary-nav.fixed .primary-nav__logo-scroll {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
opacity: 1;
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-nav__logo-image {
|
|
||||||
display: block;
|
|
||||||
width: auto;
|
|
||||||
height: 27px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-nav__burger {
|
|
||||||
width: 60px;
|
|
||||||
height: 48px;
|
|
||||||
border-right: 1px solid silver;
|
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
background-image: url('data:image/svg+xml;utf8,<svg width="23" height="16" viewBox="0 0 23 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0 0H23V2H0V0Z" fill="%23000000"/><path d="M0 7H23V9H0V7Z" fill="%23000000"/><path d="M0 14H23V16H0V14Z" fill="%23000000"/></svg>');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
background-size: 23px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-nav__burger:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-nav__list {
|
|
||||||
display: none;
|
|
||||||
flex: 1;
|
|
||||||
font-size: .875rem;
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
gap: 0;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-nav__item {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-nav__link {
|
|
||||||
text-decoration: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 1rem;
|
|
||||||
position: relative;
|
|
||||||
border-top: 1px solid currentColor;
|
|
||||||
transition: padding-top 0.2s ease, border-top-width 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-nav__link:hover {
|
|
||||||
border-top-width: 4px;
|
|
||||||
padding-top: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.primary-nav__list {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
@@ -4,6 +4,8 @@ import Author from '@components/AuthorDisplay.astro';
|
|||||||
import Subscribe from '@components/SubscribePost.astro';
|
import Subscribe from '@components/SubscribePost.astro';
|
||||||
import ShareButtons from '@components/ShareButtons.astro';
|
import ShareButtons from '@components/ShareButtons.astro';
|
||||||
import EmbeddedPost from '@components/EmbeddedPost.astro'; // шаблоны ссылок на статьи
|
import EmbeddedPost from '@components/EmbeddedPost.astro'; // шаблоны ссылок на статьи
|
||||||
|
import RelatedPosts from '@components/Content/RelatedPosts.astro';
|
||||||
|
import MoreArticles from '@components/Content/MoreArticles.astro';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -73,6 +75,8 @@ const { post, pageInfo } = Astro.props;
|
|||||||
<div>Новость не найдена</div>
|
<div>Новость не найдена</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Блок с тегами */}
|
{/* Блок с тегами */}
|
||||||
{post.tags?.nodes && post.tags.nodes.length > 0 && (
|
{post.tags?.nodes && post.tags.nodes.length > 0 && (
|
||||||
<div class="tags-block">
|
<div class="tags-block">
|
||||||
@@ -91,3 +95,5 @@ const { post, pageInfo } = Astro.props;
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<RelatedPosts />
|
||||||
|
<MoreArticles />
|
||||||
@@ -7,7 +7,7 @@ import HeaderLine from '../components/Header/HeaderLine.astro';
|
|||||||
|
|
||||||
import Footer from '../components/Footer.astro';
|
import Footer from '../components/Footer.astro';
|
||||||
|
|
||||||
import '../styles/global.css';
|
import '../styles/main.css';
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import HeaderLine from '../components/Header/HeaderLine.astro';
|
|||||||
|
|
||||||
import Footer from '../components/Footer.astro';
|
import Footer from '../components/Footer.astro';
|
||||||
|
|
||||||
import '../styles/global.css';
|
import '../styles/main.css';
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// lib/api/menu-api.ts
|
// lib/api/menu-api.ts
|
||||||
|
|
||||||
import { fetchGraphQL } from './graphql-client.js';
|
import { fetchGraphQL } from './graphql-client.js';
|
||||||
|
import { cache } from '@lib/cache/manager.js';
|
||||||
|
import { CACHE_TTL } from '@lib/cache/cache-ttl';
|
||||||
|
|
||||||
export interface MenuItem {
|
export interface MenuItem {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -40,26 +42,40 @@ export type MenuIdentifier =
|
|||||||
* Получить меню по идентификатору
|
* Получить меню по идентификатору
|
||||||
*/
|
*/
|
||||||
export async function fetchMenu(identifier: MenuIdentifier): Promise<Menu | null> {
|
export async function fetchMenu(identifier: MenuIdentifier): Promise<Menu | null> {
|
||||||
try {
|
// Создаем ключ кеша на основе типа и значения
|
||||||
// Определяем тип запроса на основе переданного идентификатора
|
let cacheKey;
|
||||||
if ('id' in identifier) {
|
if ('id' in identifier) cacheKey = `menu:id:${identifier.id}`;
|
||||||
return await fetchMenuById(identifier.id);
|
else if ('location' in identifier) cacheKey = `menu:location:${identifier.location}`;
|
||||||
}
|
else if ('slug' in identifier) cacheKey = `menu:slug:${identifier.slug}`;
|
||||||
if ('location' in identifier) {
|
else if ('name' in identifier) cacheKey = `menu:name:${identifier.name}`;
|
||||||
return await fetchMenuByLocation(identifier.location);
|
else return null;
|
||||||
}
|
|
||||||
if ('slug' in identifier) {
|
return await cache.wrap(
|
||||||
return await fetchMenuBySlug(identifier.slug);
|
cacheKey,
|
||||||
}
|
async () => {
|
||||||
if ('name' in identifier) {
|
try {
|
||||||
return await fetchMenuByName(identifier.name);
|
// Определяем тип запроса на основе переданного идентификатора
|
||||||
}
|
if ('id' in identifier) {
|
||||||
|
return await fetchMenuById(identifier.id);
|
||||||
return null;
|
}
|
||||||
} catch (error) {
|
if ('location' in identifier) {
|
||||||
console.error('Error fetching menu:', error);
|
return await fetchMenuByLocation(identifier.location);
|
||||||
return null;
|
}
|
||||||
}
|
if ('slug' in identifier) {
|
||||||
|
return await fetchMenuBySlug(identifier.slug);
|
||||||
|
}
|
||||||
|
if ('name' in identifier) {
|
||||||
|
return await fetchMenuByName(identifier.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching menu:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ ttl: CACHE_TTL.MENU } // Используем константу из cache-ttl
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -294,22 +310,38 @@ export function buildMenuHierarchy(menuItems: MenuItem[]): MenuItem[] {
|
|||||||
* Получить меню в виде иерархической структуры
|
* Получить меню в виде иерархической структуры
|
||||||
*/
|
*/
|
||||||
export async function getHierarchicalMenu(identifier: MenuIdentifier): Promise<MenuItem[]> {
|
export async function getHierarchicalMenu(identifier: MenuIdentifier): Promise<MenuItem[]> {
|
||||||
const menu = await fetchMenu(identifier);
|
const cacheKey = `menu:hierarchical:${JSON.stringify(identifier)}`;
|
||||||
if (!menu || !menu.menuItems?.nodes?.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildMenuHierarchy(menu.menuItems.nodes);
|
return await cache.wrap(
|
||||||
|
cacheKey,
|
||||||
|
async () => {
|
||||||
|
const menu = await fetchMenu(identifier);
|
||||||
|
if (!menu || !menu.menuItems?.nodes?.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildMenuHierarchy(menu.menuItems.nodes);
|
||||||
|
},
|
||||||
|
{ ttl: CACHE_TTL.MENU }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получить меню в виде плоского списка
|
* Получить меню в виде плоского списка
|
||||||
*/
|
*/
|
||||||
export async function getFlatMenu(identifier: MenuIdentifier): Promise<MenuItem[]> {
|
export async function getFlatMenu(identifier: MenuIdentifier): Promise<MenuItem[]> {
|
||||||
const menu = await fetchMenu(identifier);
|
const cacheKey = `menu:flat:${JSON.stringify(identifier)}`;
|
||||||
if (!menu || !menu.menuItems?.nodes?.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return menu.menuItems.nodes.sort((a, b) => a.order - b.order);
|
return await cache.wrap(
|
||||||
}
|
cacheKey,
|
||||||
|
async () => {
|
||||||
|
const menu = await fetchMenu(identifier);
|
||||||
|
if (!menu || !menu.menuItems?.nodes?.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return menu.menuItems.nodes.sort((a, b) => a.order - b.order);
|
||||||
|
},
|
||||||
|
{ ttl: CACHE_TTL.MENU }
|
||||||
|
);
|
||||||
|
}
|
||||||
85
src/lib/api/pages.ts
Normal file
85
src/lib/api/pages.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { fetchGraphQL } from './graphql-client.js';
|
||||||
|
|
||||||
|
/** подключаем страницу по slug */
|
||||||
|
interface PageData {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
image: string | null;
|
||||||
|
imageAlt: string;
|
||||||
|
type: 'page';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Интерфейс для ответа GraphQL
|
||||||
|
interface GraphQLResponse {
|
||||||
|
data?: {
|
||||||
|
pages?: {
|
||||||
|
nodes: Array<{
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
slug: string;
|
||||||
|
featuredImage?: {
|
||||||
|
node: {
|
||||||
|
sourceUrl: string;
|
||||||
|
altText: string;
|
||||||
|
};
|
||||||
|
} | null;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPageBySlug(slug: string): Promise<PageData | null> {
|
||||||
|
if (!slug) return null;
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
query GetPageBySlug($slug: String!) {
|
||||||
|
pages(where: {name: $slug}) {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
content
|
||||||
|
slug
|
||||||
|
featuredImage {
|
||||||
|
node {
|
||||||
|
sourceUrl(size: LARGE)
|
||||||
|
altText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Получаем данные
|
||||||
|
const response = await fetchGraphQL(query, { slug });
|
||||||
|
|
||||||
|
// ПРОВЕРЯЕМ СТРУКТУРУ ОТВЕТА
|
||||||
|
console.log('🔍 FULL RESPONSE:', JSON.stringify(response, null, 2));
|
||||||
|
|
||||||
|
// Пробуем разные варианты доступа к данным
|
||||||
|
const pages = response?.data?.pages?.nodes || response?.pages?.nodes || [];
|
||||||
|
const page = pages[0];
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
console.log('❌ No page found for slug:', slug);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Page found:', page.title);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: page.id,
|
||||||
|
title: page.title,
|
||||||
|
content: page.content,
|
||||||
|
image: page.featuredImage?.node?.sourceUrl || null,
|
||||||
|
imageAlt: page.featuredImage?.node?.altText || page.title || '',
|
||||||
|
type: 'page'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error in getPageBySlug:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,101 +11,156 @@ export interface AnewsPost {
|
|||||||
date: string;
|
date: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getLatestPosts(first = 14, after = null) {
|
|
||||||
// Создаем уникальный ключ для кэша
|
|
||||||
const cacheKey = `latest-posts:${first}:${after || 'first-page'}`;
|
export async function getLatestPosts(first = 14, after = null, excludeIds = []) {
|
||||||
|
// Нормализуем excludeIds - работаем только с databaseId (числа или строки)
|
||||||
|
const excludeArray = Array.isArray(excludeIds)
|
||||||
|
? excludeIds.filter(id => id != null).map(id => id.toString())
|
||||||
|
: (excludeIds ? [excludeIds.toString()] : []);
|
||||||
|
|
||||||
|
// Создаем уникальный ключ для кэша с учетом исключений
|
||||||
|
const excludeKey = excludeArray.length ? `exclude:${excludeArray.sort().join(',')}` : '';
|
||||||
|
const cacheKey = `latest-posts:${first}:${after || 'first-page'}${excludeKey ? `:${excludeKey}` : ''}`;
|
||||||
|
|
||||||
return await cache.wrap(
|
return await cache.wrap(
|
||||||
cacheKey,
|
cacheKey,
|
||||||
async () => {
|
async () => {
|
||||||
const query = `
|
// Функция для выполнения запроса
|
||||||
query GetLatestProfileArticles($first: Int!, $after: String) {
|
const fetchPosts = async (limit, cursor) => {
|
||||||
profileArticles(
|
const query = `
|
||||||
first: $first
|
query GetLatestProfileArticles($first: Int!, $after: String) {
|
||||||
after: $after
|
profileArticles(
|
||||||
where: { orderby: { field: DATE, order: DESC } }
|
first: $first
|
||||||
) {
|
after: $after
|
||||||
pageInfo {
|
where: { orderby: { field: DATE, order: DESC } }
|
||||||
hasNextPage
|
) {
|
||||||
endCursor
|
pageInfo {
|
||||||
}
|
hasNextPage
|
||||||
edges {
|
endCursor
|
||||||
cursor
|
}
|
||||||
node {
|
edges {
|
||||||
id
|
cursor
|
||||||
databaseId
|
node {
|
||||||
title
|
databaseId
|
||||||
uri
|
title
|
||||||
date
|
uri
|
||||||
featuredImage {
|
date
|
||||||
node {
|
featuredImage {
|
||||||
sourceUrl(size: LARGE)
|
node {
|
||||||
altText
|
sourceUrl(size: LARGE)
|
||||||
}
|
altText
|
||||||
}
|
}
|
||||||
author {
|
}
|
||||||
node {
|
author {
|
||||||
id
|
node {
|
||||||
name
|
id
|
||||||
firstName
|
name
|
||||||
lastName
|
firstName
|
||||||
avatar {
|
lastName
|
||||||
url
|
avatar {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
coauthors {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
url
|
||||||
|
description
|
||||||
|
}
|
||||||
|
categories {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
color
|
||||||
|
slug
|
||||||
|
uri
|
||||||
|
databaseId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tags {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
uri
|
|
||||||
}
|
}
|
||||||
}
|
`;
|
||||||
# Соавторы как массив
|
|
||||||
coauthors {
|
return await fetchGraphQL(query, { first: limit, after: cursor });
|
||||||
id
|
};
|
||||||
name
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
url
|
|
||||||
description
|
|
||||||
}
|
|
||||||
categories {
|
|
||||||
nodes {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
color
|
|
||||||
slug
|
|
||||||
uri
|
|
||||||
databaseId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tags {
|
|
||||||
nodes {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
uri
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const data = await fetchGraphQL(query, { first, after });
|
// Если нет исключений, просто возвращаем результат
|
||||||
|
if (excludeArray.length === 0) {
|
||||||
|
const data = await fetchPosts(first, after);
|
||||||
|
const posts = data.profileArticles?.edges?.map(edge => edge.node) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
posts,
|
||||||
|
pageInfo: data.profileArticles?.pageInfo || { hasNextPage: false, endCursor: null }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Логика с исключениями - дозагружаем недостающие
|
||||||
|
let allPosts = [];
|
||||||
|
let currentAfter = after;
|
||||||
|
let hasMore = true;
|
||||||
|
let totalNeeded = first;
|
||||||
|
|
||||||
// Преобразуем edges в nodes
|
// Продолжаем загрузку пока не наберем нужное количество или не кончатся посты
|
||||||
const posts = data.profileArticles?.edges?.map(edge => edge.node) || [];
|
while (allPosts.length < totalNeeded && hasMore) {
|
||||||
|
// Запрашиваем с запасом, чтобы компенсировать исключения
|
||||||
|
const fetchLimit = Math.max(totalNeeded - allPosts.length + excludeArray.length, 1);
|
||||||
|
|
||||||
|
const data = await fetchPosts(fetchLimit, currentAfter);
|
||||||
|
const edges = data.profileArticles?.edges || [];
|
||||||
|
const pageInfo = data.profileArticles?.pageInfo;
|
||||||
|
|
||||||
|
// Фильтруем исключенные ID - сравниваем ТОЛЬКО databaseId
|
||||||
|
const newPosts = edges
|
||||||
|
.map(edge => edge.node)
|
||||||
|
.filter(node => !excludeArray.includes(node.databaseId?.toString()));
|
||||||
|
|
||||||
|
allPosts = [...allPosts, ...newPosts];
|
||||||
|
|
||||||
|
// Обновляем курсор для следующей страницы
|
||||||
|
currentAfter = pageInfo?.endCursor;
|
||||||
|
hasMore = pageInfo?.hasNextPage && edges.length > 0;
|
||||||
|
|
||||||
|
// Защита от бесконечного цикла (если что-то пошло не так)
|
||||||
|
if (edges.length === 0) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обрезаем до нужного количества
|
||||||
|
const finalPosts = allPosts.slice(0, first);
|
||||||
|
|
||||||
|
// Определяем, есть ли еще страницы
|
||||||
|
const hasNextPage = allPosts.length > first || hasMore;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
posts,
|
posts: finalPosts,
|
||||||
pageInfo: data.profileArticles?.pageInfo || { hasNextPage: false, endCursor: null }
|
pageInfo: {
|
||||||
|
hasNextPage,
|
||||||
|
endCursor: currentAfter // Последний использованный курсор
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ ttl: CACHE_TTL.POSTS } // из конфигурации
|
{ ttl: CACHE_TTL.POSTS }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function getPostsByCategory(slug, first = 14, after = null) {
|
export async function getPostsByCategory(slug, first = 14, after = null) {
|
||||||
// Создаем уникальный ключ для кэша
|
// Создаем уникальный ключ для кэша
|
||||||
const cacheKey = `category-posts:${slug}:${first}:${after || 'first-page'}`;
|
const cacheKey = `category-posts:${slug}:${first}:${after || 'first-page'}`;
|
||||||
@@ -244,6 +299,50 @@ export async function getPostsByCategory(slug, first = 14, after = null) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getRelatedPosts() {
|
||||||
|
|
||||||
|
const cacheKey = 'related-posts-widget';
|
||||||
|
|
||||||
|
return await cache.wrap(
|
||||||
|
cacheKey,
|
||||||
|
async () => {
|
||||||
|
const query = `
|
||||||
|
query GetRelatedPosts {
|
||||||
|
profileArticles(
|
||||||
|
first: 3
|
||||||
|
where: { orderby: { field: DATE, order: DESC } }
|
||||||
|
) {
|
||||||
|
nodes {
|
||||||
|
title
|
||||||
|
uri
|
||||||
|
featuredImage {
|
||||||
|
node {
|
||||||
|
sourceUrl(size: THUMBNAIL) # THUMBNAIL для компактности
|
||||||
|
altText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await fetchGraphQL(query);
|
||||||
|
|
||||||
|
// Форматируем данные для удобного использования
|
||||||
|
const posts = data.profileArticles?.nodes?.map(post => ({
|
||||||
|
title: post.title,
|
||||||
|
// Убираем домен из URL, оставляем только путь
|
||||||
|
url: post.uri.replace(/^https?:\/\/[^\/]+/, ''),
|
||||||
|
image: post.featuredImage?.node?.sourceUrl || null,
|
||||||
|
imageAlt: post.featuredImage?.node?.altText || post.title
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return posts;
|
||||||
|
},
|
||||||
|
{ ttl: CACHE_TTL.POSTS } // Кэш на 1 час, так как это виджет
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getPostsByTag(slug, first = 14, after = null) {
|
export async function getPostsByTag(slug, first = 14, after = null) {
|
||||||
// Создаем уникальный ключ для кэша
|
// Создаем уникальный ключ для кэша
|
||||||
|
|||||||
3
src/lib/cache/cache-ttl.ts
vendored
3
src/lib/cache/cache-ttl.ts
vendored
@@ -4,7 +4,8 @@
|
|||||||
export const CACHE_TTL = {
|
export const CACHE_TTL = {
|
||||||
TAXONOMY: parseInt(import.meta.env.CACHE_TAXONOMY_TTL || '3600'),
|
TAXONOMY: parseInt(import.meta.env.CACHE_TAXONOMY_TTL || '3600'),
|
||||||
POSTS: parseInt(import.meta.env.CACHE_POST_TTL || '1800'),
|
POSTS: parseInt(import.meta.env.CACHE_POST_TTL || '1800'),
|
||||||
AUTHOR: parseInt(import.meta.env.CACHE_AUTHOR_TTL || '8')
|
AUTHOR: parseInt(import.meta.env.CACHE_AUTHOR_TTL || '8'),
|
||||||
|
MENU: parseInt(import.meta.env.CACHE_MENU_TTL || '800000')
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Для отключения кэша
|
// Для отключения кэша
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { wpInfo } from './wpInfo';
|
||||||
|
|
||||||
export interface PageTypeInfo {
|
export interface PageTypeInfo {
|
||||||
type: 'single' | 'archive' | 'home' | 'unknown';
|
type: 'single' | 'archive' | 'home' | 'unknown';
|
||||||
contentType?: 'news' | 'post';
|
contentType?: 'news' | 'post';
|
||||||
@@ -5,14 +7,33 @@ export interface PageTypeInfo {
|
|||||||
postSlug?: string;
|
postSlug?: string;
|
||||||
postId?: number;
|
postId?: number;
|
||||||
page: number;
|
page: number;
|
||||||
|
pageSlug?: string;
|
||||||
uriParts: string[];
|
uriParts: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function detectPageType(uri: string): PageTypeInfo {
|
export function detectPageType(uri: string): PageTypeInfo {
|
||||||
|
//console.log('🔍 detectPageType INPUT:', uri);
|
||||||
|
|
||||||
|
// Убираем query параметры если есть
|
||||||
|
const uriWithoutQuery = uri.split('?')[0];
|
||||||
|
|
||||||
// Убираем слэши по краям
|
// Убираем слэши по краям
|
||||||
const cleanUri = uri.replace(/^\/|\/$/g, '');
|
const cleanUri = uriWithoutQuery.replace(/^\/|\/$/g, '');
|
||||||
const parts = cleanUri ? cleanUri.split('/') : [];
|
|
||||||
|
console.log('📌 Clean URI:', cleanUri);
|
||||||
|
|
||||||
|
// Если URI пустой - это главная
|
||||||
|
if (cleanUri === '') {
|
||||||
|
console.log('🏠 Home page detected');
|
||||||
|
return {
|
||||||
|
type: 'home',
|
||||||
|
page: 1,
|
||||||
|
uriParts: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = cleanUri.split('/');
|
||||||
|
console.log('📦 Parts:', parts);
|
||||||
|
|
||||||
// Ищем пагинацию
|
// Ищем пагинацию
|
||||||
const processedParts: string[] = [];
|
const processedParts: string[] = [];
|
||||||
@@ -23,20 +44,27 @@ export function detectPageType(uri: string): PageTypeInfo {
|
|||||||
const pageNum = parseInt(parts[i + 1]);
|
const pageNum = parseInt(parts[i + 1]);
|
||||||
if (pageNum > 0) {
|
if (pageNum > 0) {
|
||||||
page = pageNum;
|
page = pageNum;
|
||||||
i++; // Пропускаем номер страницы
|
i++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processedParts.push(parts[i]);
|
processedParts.push(parts[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//console.log('🔄 Processed parts:', processedParts);
|
||||||
|
|
||||||
// Определяем, это новость или нет
|
// Определяем, это новость или нет
|
||||||
const isNews = processedParts[0] === 'news';
|
const isNews = processedParts[0] === 'news';
|
||||||
|
//console.log('📰 isNews:', isNews);
|
||||||
|
|
||||||
// Для анализа убираем 'news' из начала если есть
|
// Для анализа убираем 'news' из начала если есть
|
||||||
const partsWithoutNews = isNews ? processedParts.slice(1) : processedParts;
|
const partsWithoutNews = isNews ? processedParts.slice(1) : processedParts;
|
||||||
const partsCount = partsWithoutNews.length;
|
//console.log('✂️ Parts without news:', partsWithoutNews);
|
||||||
|
|
||||||
|
const partsCount = partsWithoutNews.length;
|
||||||
|
//console.log('🔢 Parts count:', partsCount);
|
||||||
|
|
||||||
|
// Домашняя страница
|
||||||
if (partsCount === 0) {
|
if (partsCount === 0) {
|
||||||
return {
|
return {
|
||||||
type: 'home',
|
type: 'home',
|
||||||
@@ -45,42 +73,66 @@ export function detectPageType(uri: string): PageTypeInfo {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Проверяем, является ли последний сегмент постом (содержит ID)
|
||||||
|
const lastPart = partsWithoutNews[partsWithoutNews.length - 1];
|
||||||
|
//console.log('🔚 Last part:', lastPart);
|
||||||
|
|
||||||
|
const match = lastPart.match(/-(\d+)$/);
|
||||||
|
//console.log('🎯 Post ID match:', match);
|
||||||
|
|
||||||
// Одиночный пост
|
// Одиночный пост
|
||||||
if (partsCount === 1 || partsCount === 2) {
|
if (match) {
|
||||||
const lastPart = partsWithoutNews[partsWithoutNews.length - 1];
|
const id = parseInt(match[1]);
|
||||||
const match = lastPart.match(/-(\d+)$/);
|
if (id > 0) {
|
||||||
|
const slugWithoutId = lastPart.substring(0, lastPart.lastIndexOf('-'));
|
||||||
if (match) {
|
|
||||||
const id = parseInt(match[1]);
|
//console.log('📄 Single post detected:', { id, slugWithoutId });
|
||||||
if (id > 0) {
|
|
||||||
const slugWithoutId = lastPart.substring(0, lastPart.lastIndexOf('-'));
|
return {
|
||||||
|
type: 'single',
|
||||||
return {
|
contentType: isNews ? 'news' : 'post',
|
||||||
type: 'single',
|
categorySlug: partsCount === 2 ? partsWithoutNews[0] : undefined,
|
||||||
contentType: isNews ? 'news' : 'post', // Теперь правильно
|
postSlug: slugWithoutId,
|
||||||
categorySlug: partsCount === 2 ? partsWithoutNews[0] : undefined,
|
postId: id,
|
||||||
postSlug: slugWithoutId,
|
page,
|
||||||
postId: id,
|
uriParts: processedParts
|
||||||
page,
|
};
|
||||||
uriParts: processedParts // Сохраняем исходные части
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Рубрика
|
// Проверяем, является ли первый сегмент существующей рубрикой
|
||||||
if (partsCount === 1) {
|
if (partsCount === 1) {
|
||||||
return {
|
const potentialCategorySlug = partsWithoutNews[0];
|
||||||
type: 'archive',
|
//console.log('📁 Checking if category exists:', potentialCategorySlug);
|
||||||
contentType: isNews ? 'news' : undefined,
|
|
||||||
categorySlug: partsWithoutNews[0],
|
// Ждем загрузки рубрик если нужно
|
||||||
page,
|
if (!wpInfo.isLoaded()) {
|
||||||
uriParts: processedParts
|
//console.log('⏳ Waiting for categories to load...');
|
||||||
};
|
// В синхронной функции не можем ждать, поэтому проверяем позже
|
||||||
|
}
|
||||||
|
|
||||||
|
const category = wpInfo.getCategoryBySlug(potentialCategorySlug);
|
||||||
|
//console.log('🏷️ Category found:', category ? 'YES' : 'NO');
|
||||||
|
|
||||||
|
if (category) {
|
||||||
|
//console.log('📁 Archive detected (existing category):', potentialCategorySlug);
|
||||||
|
return {
|
||||||
|
type: 'archive',
|
||||||
|
contentType: isNews ? 'news' : undefined,
|
||||||
|
categorySlug: potentialCategorySlug,
|
||||||
|
page,
|
||||||
|
uriParts: processedParts
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если это не рубрика - это обычная страница
|
||||||
|
const pageSlug = partsWithoutNews.join('/');
|
||||||
|
//console.log('📄 PAGE DETECTED with slug:', pageSlug);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'unknown',
|
type: 'unknown',
|
||||||
|
pageSlug: pageSlug,
|
||||||
page,
|
page,
|
||||||
uriParts: processedParts
|
uriParts: processedParts
|
||||||
};
|
};
|
||||||
|
|||||||
31
src/lib/utils/url.ts
Normal file
31
src/lib/utils/url.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Нормализует URL, заменяя полный домен на относительный путь
|
||||||
|
* @param url - исходный URL
|
||||||
|
* @returns нормализованный URL (путь + query + hash)
|
||||||
|
*/
|
||||||
|
export function normalizeMenuUrl(url: string): string {
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
// Возвращаем путь с query параметрами и хешем, если они есть
|
||||||
|
return urlObj.pathname + urlObj.search + urlObj.hash;
|
||||||
|
} catch {
|
||||||
|
// Если URL невалидный, возвращаем как есть
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, является ли URL внешним
|
||||||
|
* @param url - URL для проверки
|
||||||
|
* @returns true если URL внешний
|
||||||
|
*/
|
||||||
|
export function isExternalUrl(url: string): boolean {
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
return urlObj.protocol.startsWith('http') &&
|
||||||
|
!urlObj.hostname.includes('localhost') &&
|
||||||
|
!urlObj.hostname.includes('127.0.0.1');
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,92 +4,91 @@ import NewsSingle from '@components/NewsSingle.astro';
|
|||||||
import ContentGrid from '@components/ContentGrid.astro';
|
import ContentGrid from '@components/ContentGrid.astro';
|
||||||
|
|
||||||
import { getNodeByURI } from '@lib/api/all';
|
import { getNodeByURI } from '@lib/api/all';
|
||||||
import { getProfileArticleById, getPostsByCategory } from '@lib/api/posts'; //логика
|
import { getProfileArticleById, getPostsByCategory } from '@lib/api/posts';
|
||||||
|
import { getPageBySlug } from '@lib/api/pages';
|
||||||
|
|
||||||
|
import { getCategory } from '@lib/api/categories';
|
||||||
import { getCategory } from '@lib/api/categories'; //логика
|
|
||||||
import { wpInfo } from '@lib/wpInfo';
|
import { wpInfo } from '@lib/wpInfo';
|
||||||
import { detectPageType } from '@lib/detect-page-type';
|
import { detectPageType } from '@lib/detect-page-type';
|
||||||
|
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
const pathname = Astro.url.pathname;
|
const pathname = Astro.url.pathname;
|
||||||
const pageInfo = detectPageType(pathname); //определяем тип страницы
|
|
||||||
|
|
||||||
|
// Убеждаемся что рубрики загружены перед определением типа
|
||||||
let response;
|
|
||||||
let article = null;
|
|
||||||
let posts = null;
|
|
||||||
let result = null;
|
|
||||||
let category;
|
|
||||||
|
|
||||||
// Определяем категорию
|
|
||||||
if (!wpInfo.isLoaded()) {
|
if (!wpInfo.isLoaded()) {
|
||||||
await wpInfo.fetchAllCategories();
|
await wpInfo.fetchAllCategories();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pageInfo = detectPageType(pathname);
|
||||||
|
|
||||||
|
let article = null;
|
||||||
|
let posts = null;
|
||||||
|
let result = null;
|
||||||
|
let category;
|
||||||
|
let page = null;
|
||||||
|
|
||||||
|
// Получаем категорию если есть
|
||||||
category = wpInfo.getCategoryBySlug(pageInfo.categorySlug);
|
category = wpInfo.getCategoryBySlug(pageInfo.categorySlug);
|
||||||
|
|
||||||
|
let title = 'Профиль';
|
||||||
|
|
||||||
let title = 'Профиль'; //title page
|
console.log(pageInfo);
|
||||||
|
|
||||||
if (pageInfo.type === 'single') { //одиночная статья
|
if (pageInfo.type === 'single') {
|
||||||
|
try {
|
||||||
try {
|
article = await getProfileArticleById(pageInfo.postId);
|
||||||
article = await getProfileArticleById(pageInfo.postId); //получвем данные поста
|
title = article?.title || title;
|
||||||
title=article.title
|
} catch (error) {
|
||||||
} catch (error) {
|
console.error('Error fetching node:', error);
|
||||||
console.error('Error fetching node:', error);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
} else if (pageInfo.type === 'archive') {
|
} else if (pageInfo.type === 'archive') {
|
||||||
|
result = await getPostsByCategory(pageInfo.categorySlug, 21);
|
||||||
result = await getPostsByCategory(pageInfo.categorySlug, 21); //получвем данные поста
|
posts = result?.posts;
|
||||||
posts = result.posts;
|
} else if (pageInfo.type === 'unknown' && pageInfo.pageSlug) {
|
||||||
|
try {
|
||||||
|
page = await getPageBySlug(pageInfo.pageSlug);
|
||||||
}
|
if (page) {
|
||||||
|
title = page.title;
|
||||||
|
}
|
||||||
// ISR кэширование
|
} catch (error) {
|
||||||
//Astro.response.headers.set(
|
console.error('Error fetching page:', error);
|
||||||
// 'Cache-Control',
|
}
|
||||||
// 'public, s-maxage=3600, stale-while-revalidate=86400'
|
}
|
||||||
//);
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<ContentLayout
|
<ContentLayout
|
||||||
title={title}
|
title={title}
|
||||||
description="Информационное агентство Деловой журнал Профиль"
|
description="Информационное агентство Деловой журнал Профиль"
|
||||||
category={category}
|
category={category}
|
||||||
>
|
>
|
||||||
|
|
||||||
{/* Page (страница) */}
|
{/* СТРАНИЦА */}
|
||||||
{pageInfo.type === 'unknown' && (
|
{pageInfo.type === 'unknown' && page && (
|
||||||
<div>✅ Это страница: {pageInfo.pageSlug}</div>
|
<div class="article-wrapper">
|
||||||
)}
|
<article class="news-single">
|
||||||
|
<h1>{page.title}</h1>
|
||||||
|
<div set:html={page.content} />
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Single post */}
|
||||||
|
{pageInfo.type === 'single' && article && (
|
||||||
|
<NewsSingle post={article} pageInfo={pageInfo} />
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Single post */}
|
{/* Category archive */}
|
||||||
{pageInfo.type === 'single' && article && (
|
{pageInfo.type === 'archive' && posts && (
|
||||||
<NewsSingle post={article} pageInfo={pageInfo} />
|
<ContentGrid
|
||||||
)}
|
items={posts}
|
||||||
|
pageInfo={result?.pageInfo}
|
||||||
|
slug={pageInfo.categorySlug}
|
||||||
{/* Category archive */}
|
showCount={false}
|
||||||
{pageInfo.type === 'archive' && posts && (
|
type='category'
|
||||||
<ContentGrid
|
perLoad={12}
|
||||||
items={posts}
|
gridColumns={3}
|
||||||
pageInfo={result.pageInfo}
|
/>
|
||||||
slug={pageInfo.categorySlug}
|
)}
|
||||||
showCount={false}
|
|
||||||
type='category'
|
|
||||||
perLoad={12}
|
|
||||||
gridColumns={3}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</ContentLayout>
|
|
||||||
|
|
||||||
|
</ContentLayout>
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
import ContentLayout from '@layouts/ContentLayout.astro';
|
import ContentLayout from '@layouts/ContentLayout.astro';
|
||||||
import ContentGrid from '@components/ContentGrid.astro';
|
import ContentGrid from '@components/ContentGrid.astro';
|
||||||
|
|
||||||
import { getLatestPosts } from '@api/posts.js';
|
import { getLatestPosts } from '@api/posts';
|
||||||
|
|
||||||
const { posts, pageInfo } = await getLatestPosts(41); // Сразу деструктурируем
|
const { posts, pageInfo } = await getLatestPosts(41);
|
||||||
|
|
||||||
|
|
||||||
//ISR
|
//ISR
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
---
|
---
|
||||||
import { getSiteInfo } from "../lib/wp-api.js";
|
import { getSiteInfo } from "@lib/wp-api.js";
|
||||||
import { getLatestPosts } from '@api/posts.js';
|
import { getLatestPosts } from '@api/posts';
|
||||||
|
import { getLatestMainPost } from '@lib/api/main-posts';
|
||||||
import { fetchWPRestGet } from "../lib/api/wp-rest-get-client";
|
import { getLatestColonPost } from '@lib/api/colon-posts';
|
||||||
|
|
||||||
|
|
||||||
import '../styles/home.css';
|
|
||||||
|
|
||||||
const site = await getSiteInfo();
|
|
||||||
const { posts, pageInfo } = await getLatestPosts(41); // Сразу деструктурируем
|
|
||||||
|
|
||||||
|
import { fetchWPRestGet } from "@lib/api/wp-rest-get-client";
|
||||||
|
|
||||||
// визуальные компоненты
|
// визуальные компоненты
|
||||||
import MainLayout from '@layouts/MainLayout.astro';
|
import MainLayout from '@layouts/MainLayout.astro';
|
||||||
@@ -19,6 +14,39 @@ import MainLine from '@components/MainLine.astro';
|
|||||||
import HomeNews from "@components/HomeNews.astro";
|
import HomeNews from "@components/HomeNews.astro";
|
||||||
|
|
||||||
|
|
||||||
|
//import '../styles/home.css';
|
||||||
|
import '../styles/main.css';
|
||||||
|
|
||||||
|
|
||||||
|
//получаем главный пост
|
||||||
|
const mainPost = await getLatestMainPost();
|
||||||
|
const mainPostId = mainPost?.databaseId;
|
||||||
|
|
||||||
|
//колонка
|
||||||
|
const colonPost = await getLatestColonPost();
|
||||||
|
const colonPostId = mainPost?.databaseId;
|
||||||
|
|
||||||
|
|
||||||
|
// Создаем массив исключений
|
||||||
|
const excludeIds = [];
|
||||||
|
|
||||||
|
// Добавляем ID если они существуют
|
||||||
|
if (mainPostId) {
|
||||||
|
excludeIds.push(mainPostId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colonPostId) {
|
||||||
|
excludeIds.push(colonPostId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const site = await getSiteInfo();
|
||||||
|
const { posts, pageInfo } = await getLatestPosts(41, null, excludeIds ); // Сразу деструктурируем
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//ISR
|
//ISR
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
---
|
---
|
||||||
@@ -30,7 +58,10 @@ export const prerender = false;
|
|||||||
|
|
||||||
<!-- index.astro -->
|
<!-- index.astro -->
|
||||||
|
|
||||||
<MainLine />
|
<MainLine
|
||||||
|
mainPost={mainPost}
|
||||||
|
colonPost={colonPost}
|
||||||
|
/>excludeIds
|
||||||
|
|
||||||
|
|
||||||
<ContentGrid
|
<ContentGrid
|
||||||
@@ -38,7 +69,7 @@ export const prerender = false;
|
|||||||
pageInfo={pageInfo}
|
pageInfo={pageInfo}
|
||||||
type="latest"
|
type="latest"
|
||||||
perLoad={11}
|
perLoad={11}
|
||||||
showCount={true}
|
showCount={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
import ContentLayout from '@layouts/ContentLayout.astro';
|
import ContentLayout from '@layouts/ContentLayout.astro';
|
||||||
|
import ContentGrid from '@components/ContentGrid.astro';
|
||||||
|
|
||||||
|
import { getLatestAnews } from '@api/posts';
|
||||||
|
|
||||||
|
const { posts, pageInfo } = await getLatestAnews(41);
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -9,4 +14,15 @@ import ContentLayout from '@layouts/ContentLayout.astro';
|
|||||||
description=Новости
|
description=Новости
|
||||||
>
|
>
|
||||||
<h1>Все новости</h1>
|
<h1>Все новости</h1>
|
||||||
|
|
||||||
|
<ContentGrid
|
||||||
|
items={posts}
|
||||||
|
pageInfo={pageInfo}
|
||||||
|
type="latest"
|
||||||
|
gridColumns={3}
|
||||||
|
perLoad={11}
|
||||||
|
showCount={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
231
src/scripts/ContentGrid.js
Normal file
231
src/scripts/ContentGrid.js
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
// src/scripts/infinity-scroll.js
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
class InfinityScroll {
|
||||||
|
constructor(sentinelElement) {
|
||||||
|
this.grid = document.getElementById('posts-grid');
|
||||||
|
this.sentinel = sentinelElement;
|
||||||
|
this.loadingIndicator = document.getElementById('loading-indicator');
|
||||||
|
this.noMorePosts = document.getElementById('no-more-posts');
|
||||||
|
this.postsCount = document.getElementById('posts-count');
|
||||||
|
this.observer = null;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.hasMore = true;
|
||||||
|
this.endCursor = null;
|
||||||
|
this.currentIndex = 0;
|
||||||
|
this.gridColumns = 4;
|
||||||
|
this.loadMoreConfig = { type: 'latest', slug: '', gridColumns: 4 };
|
||||||
|
|
||||||
|
// Константы для разных сеток
|
||||||
|
this.CYCLE_LENGTH = {
|
||||||
|
3: 14, // Полный цикл для 3 колонок: 6 обычных + 1 большая + 6 обычных + 1 большая
|
||||||
|
4: 19 // Полный цикл для 4 колонок: 8 обычных + 1 большая + 9 обычных + 1 большая
|
||||||
|
};
|
||||||
|
|
||||||
|
this.OPTIMAL_LOADS = {
|
||||||
|
3: [9, 12, 15], // 3, 4, 5 полных рядов
|
||||||
|
4: [12, 16, 20] // 3, 4, 5 полных рядов
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (!this.sentinel || !this.grid) return;
|
||||||
|
|
||||||
|
// Получаем данные из sentinel
|
||||||
|
this.endCursor = this.sentinel.dataset.endCursor || null;
|
||||||
|
this.currentIndex = parseInt(this.sentinel.dataset.currentIndex || '0');
|
||||||
|
this.gridColumns = parseInt(this.sentinel.dataset.gridColumns || '4');
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loadMoreConfig = JSON.parse(this.sentinel.dataset.loadConfig || '{}');
|
||||||
|
} catch {
|
||||||
|
this.loadMoreConfig = { type: 'latest', slug: '', gridColumns: this.gridColumns };
|
||||||
|
}
|
||||||
|
|
||||||
|
this.observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting && !this.isLoading && this.hasMore) {
|
||||||
|
this.loadMorePosts();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rootMargin: '200px',
|
||||||
|
threshold: 0.1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.observer.observe(this.sentinel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Определяет оптимальное количество постов для загрузки
|
||||||
|
*/
|
||||||
|
getOptimalLoadCount() {
|
||||||
|
const columns = this.gridColumns;
|
||||||
|
const cycleLength = this.CYCLE_LENGTH[columns];
|
||||||
|
const position = this.currentIndex % cycleLength;
|
||||||
|
const options = this.OPTIMAL_LOADS[columns];
|
||||||
|
|
||||||
|
// Выбираем оптимальное число в зависимости от позиции в цикле
|
||||||
|
if (position < columns) {
|
||||||
|
return options[0]; // В начале цикла - 3 полных ряда
|
||||||
|
} else if (position < columns * 2) {
|
||||||
|
return options[1]; // В середине цикла - 4 полных ряда
|
||||||
|
} else {
|
||||||
|
return options[2]; // Ближе к концу - 5 полных рядов
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadMorePosts() {
|
||||||
|
if (this.isLoading || !this.hasMore) return;
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
this.showLoading();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loadCount = this.getOptimalLoadCount();
|
||||||
|
|
||||||
|
const response = await fetch('/load-more-posts', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
perLoad: loadCount,
|
||||||
|
after: this.endCursor,
|
||||||
|
type: this.loadMoreConfig?.type || 'latest',
|
||||||
|
slug: this.loadMoreConfig?.slug || '',
|
||||||
|
startIndex: this.currentIndex,
|
||||||
|
gridColumns: this.gridColumns
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка загрузки: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await response.text();
|
||||||
|
const temp = document.createElement('div');
|
||||||
|
temp.innerHTML = html;
|
||||||
|
|
||||||
|
const newSentinel = temp.querySelector('#infinity-scroll-sentinel');
|
||||||
|
const hasNextPage = !!newSentinel;
|
||||||
|
const newEndCursor = newSentinel?.dataset.endCursor || null;
|
||||||
|
|
||||||
|
const articles = temp.querySelectorAll('article');
|
||||||
|
|
||||||
|
if (articles.length > 0) {
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
articles.forEach(article => fragment.appendChild(article.cloneNode(true)));
|
||||||
|
this.grid.appendChild(fragment);
|
||||||
|
|
||||||
|
this.currentIndex += articles.length;
|
||||||
|
this.endCursor = newEndCursor;
|
||||||
|
this.hasMore = hasNextPage;
|
||||||
|
|
||||||
|
if (this.postsCount) {
|
||||||
|
this.postsCount.textContent = ` (${this.currentIndex})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sentinel) {
|
||||||
|
this.sentinel.dataset.currentIndex = String(this.currentIndex);
|
||||||
|
this.sentinel.dataset.endCursor = newEndCursor || '';
|
||||||
|
|
||||||
|
if (!hasNextPage) {
|
||||||
|
this.showNoMorePosts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.hasMore = false;
|
||||||
|
this.showNoMorePosts();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка загрузки:', error);
|
||||||
|
this.hasMore = false;
|
||||||
|
this.showError();
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading() {
|
||||||
|
if (this.loadingIndicator) {
|
||||||
|
this.loadingIndicator.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoading() {
|
||||||
|
if (this.loadingIndicator) {
|
||||||
|
this.loadingIndicator.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showNoMorePosts() {
|
||||||
|
if (this.sentinel && this.observer) {
|
||||||
|
this.observer.unobserve(this.sentinel);
|
||||||
|
this.sentinel.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.noMorePosts) {
|
||||||
|
this.noMorePosts.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError() {
|
||||||
|
if (this.noMorePosts) {
|
||||||
|
this.noMorePosts.textContent = 'Ошибка загрузки. Попробуйте обновить страницу.';
|
||||||
|
this.noMorePosts.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.observer && this.sentinel) {
|
||||||
|
this.observer.unobserve(this.sentinel);
|
||||||
|
}
|
||||||
|
if (this.observer) {
|
||||||
|
this.observer.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Глобальная функция для инициализации
|
||||||
|
window.initInfinityScroll = function(sentinelElement) {
|
||||||
|
if (!sentinelElement) {
|
||||||
|
sentinelElement = document.getElementById('infinity-scroll-sentinel');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sentinelElement) {
|
||||||
|
// Очищаем предыдущий инстанс если есть
|
||||||
|
if (window.__infinityScrollInstance) {
|
||||||
|
window.__infinityScrollInstance.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем новый инстанс
|
||||||
|
window.__infinityScrollInstance = new InfinityScroll(sentinelElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Автоматическая инициализация при загрузке страницы
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
window.initInfinityScroll();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.initInfinityScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка для динамической навигации Astro
|
||||||
|
document.addEventListener('astro:after-swap', () => {
|
||||||
|
// Переинициализируем после смены страницы
|
||||||
|
setTimeout(() => {
|
||||||
|
window.initInfinityScroll();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
12
src/scripts/cookie-consent.js
Normal file
12
src/scripts/cookie-consent.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
if (!localStorage.getItem('cookie_consent_accepted')) {
|
||||||
|
const banner = document.getElementById('cookie-consent-banner');
|
||||||
|
banner.style.display = 'block';
|
||||||
|
|
||||||
|
document.getElementById('cookie-consent-accept').addEventListener('click', function() {
|
||||||
|
localStorage.setItem('cookie_consent_accepted', 'true');
|
||||||
|
banner.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
import './ContentGrid.js';
|
import './ContentGrid.js';
|
||||||
import './embedded-content.js';
|
import './embedded-content.js';
|
||||||
|
import './cookie-consent.js';
|
||||||
|
import './toggleFooter.js';
|
||||||
68
src/scripts/toggleFooter.js
Normal file
68
src/scripts/toggleFooter.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('Footer script initialized');
|
||||||
|
|
||||||
|
// Функция toggleFooter
|
||||||
|
function toggleFooter(button) {
|
||||||
|
console.log('toggleFooter called');
|
||||||
|
|
||||||
|
const footer = button.closest('.footer');
|
||||||
|
if (!footer) {
|
||||||
|
console.error('Footer not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandedSection = footer.querySelector('.footer__expanded');
|
||||||
|
if (!expandedSection) {
|
||||||
|
console.error('Expanded section not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isExpanded = button.getAttribute('aria-expanded') === 'true';
|
||||||
|
|
||||||
|
// Обновляем состояние кнопки
|
||||||
|
button.setAttribute('aria-expanded', !isExpanded);
|
||||||
|
button.setAttribute('aria-label', isExpanded ? 'Развернуть футер' : 'Свернуть футер');
|
||||||
|
|
||||||
|
// Поворачиваем стрелку
|
||||||
|
const arrow = button.querySelector('.footer__arrow');
|
||||||
|
if (arrow) {
|
||||||
|
if (isExpanded) {
|
||||||
|
arrow.style.transform = 'rotate(0deg)';
|
||||||
|
} else {
|
||||||
|
arrow.style.transform = 'rotate(180deg)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показываем/скрываем контент
|
||||||
|
if (isExpanded) {
|
||||||
|
expandedSection.setAttribute('hidden', '');
|
||||||
|
console.log('Footer collapsed');
|
||||||
|
} else {
|
||||||
|
expandedSection.removeAttribute('hidden');
|
||||||
|
console.log('Footer expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Находим основную кнопку разворачивания
|
||||||
|
const toggleButtons = document.querySelectorAll('.footer__toggle');
|
||||||
|
toggleButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
toggleFooter(this);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Находим кнопки сворачивания
|
||||||
|
const collapseButtons = document.querySelectorAll('.footer__collapse-btn');
|
||||||
|
collapseButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const footer = this.closest('.footer');
|
||||||
|
if (footer) {
|
||||||
|
const toggleButton = footer.querySelector('.footer__toggle');
|
||||||
|
if (toggleButton) {
|
||||||
|
toggleFooter(toggleButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -10,11 +10,6 @@
|
|||||||
--overlay-padding-large: 25px 20px 20px;
|
--overlay-padding-large: 25px 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Секция постов */
|
|
||||||
.posts-section {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.posts-section h2 {
|
.posts-section h2 {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
color: #333;
|
color: #333;
|
||||||
@@ -157,8 +152,8 @@
|
|||||||
/* Для сетки 4 колонки - большие карточки на 2 колонки */
|
/* Для сетки 4 колонки - большие карточки на 2 колонки */
|
||||||
.posts-grid-4 .post-card-large {
|
.posts-grid-4 .post-card-large {
|
||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
aspect-ratio: 2 / 1;
|
aspect-ratio: 2 / 0.965;
|
||||||
padding-bottom: 50%;
|
padding-bottom: 48.3%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Для сетки 3 колонки - большие карточки на 2 колонки */
|
/* Для сетки 3 колонки - большие карточки на 2 колонки */
|
||||||
|
|||||||
152
src/styles/components/RelatedPosts.css
Normal file
152
src/styles/components/RelatedPosts.css
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
/* Сброс отступов для контейнера */
|
||||||
|
.related-posts {
|
||||||
|
padding: 40px 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Хедер с серым фоном и черной рамкой */
|
||||||
|
.related-posts__header {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-top: 4px solid #000;
|
||||||
|
padding: 15px 0 15px 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Заголовок */
|
||||||
|
.related-posts__title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flex контейнер - строго 3 колонки на десктопе */
|
||||||
|
.related-posts__flex {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Карточка поста - строго 3 в ряд на всех десктопах */
|
||||||
|
.related-post {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
background-color: transparent;
|
||||||
|
flex: 0 1 calc(33.333% - 20px); /* Изменил с 1 1 на 0 1 */
|
||||||
|
min-width: 0; /* Сбрасываем min-width для десктопа */
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Контейнер для изображения */
|
||||||
|
.related-post__image-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 66.67%; /* Соотношение сторон 3:2 */
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post__image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Плейсхолдер если нет изображения */
|
||||||
|
.related-post__image-wrapper--placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Заголовок поста */
|
||||||
|
.related-post__title {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post:hover .related-post__title {
|
||||||
|
color: #0066cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ТОЛЬКО для мобильных - один в ряд */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.related-posts {
|
||||||
|
padding: 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-posts__header {
|
||||||
|
padding: 12px 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-posts__title {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-posts__flex {
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post {
|
||||||
|
flex: 1 1 100%; /* На мобильных полная ширина */
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post__image-wrapper {
|
||||||
|
width: 100px;
|
||||||
|
padding-bottom: 75px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post__title {
|
||||||
|
font-size: 15px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Очень маленькие экраны */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.related-posts__header {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-posts__title {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post__image-wrapper {
|
||||||
|
width: 80px;
|
||||||
|
padding-bottom: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-post__title {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/styles/components/colon-post.css
Normal file
141
src/styles/components/colon-post.css
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/* Основная карточка */
|
||||||
|
.colon-post-card {
|
||||||
|
background: #ececec;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px; /* опционально, для демо */
|
||||||
|
height: 200px; /* фиксированная высота карточки */
|
||||||
|
}
|
||||||
|
|
||||||
|
.colon-post-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flex-контейнер: две части */
|
||||||
|
.split-flex {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ЛЕВЫЙ БЛОК: ровно 30% */
|
||||||
|
.left-photo {
|
||||||
|
flex: 0 0 34%; /* ширина 30%, не растягивается */
|
||||||
|
height: 100%;
|
||||||
|
background: #d4d4d4; /* фон, если нет фото */
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-link {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover; /* заполняет контейнер, сохраняя пропорции и обрезаясь */
|
||||||
|
display: block;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-img:hover {
|
||||||
|
transform: scale(1.05); /* легкий эффект при наведении */
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ПРАВЫЙ БЛОК: 70% */
|
||||||
|
.right-content {
|
||||||
|
flex: 1; /* занимает оставшееся место (70%) */
|
||||||
|
height: 100%;
|
||||||
|
padding: 16px 20px; /* внутренние отступы */
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Обёртка для контента, чтобы занять всю высоту и распределить пространство */
|
||||||
|
.content-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Заголовок жирным */
|
||||||
|
.bold-title {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #2c3e50;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-link:hover .bold-title {
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Мета-строка: прижимаем к низу */
|
||||||
|
.meta-line {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #666;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: auto; /* это прижимает мету к низу */
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
color: #aaa;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author :global(a) {
|
||||||
|
color: #2c3e50;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author :global(a:hover) {
|
||||||
|
color: #3498db;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Адаптивность */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.colon-post-card {
|
||||||
|
height: auto;
|
||||||
|
min-height: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-photo {
|
||||||
|
flex: 0 0 30%;
|
||||||
|
aspect-ratio: 1 / 1; /* сохраняем квадрат на мобильных */
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold-title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/styles/components/cookie-consent.css
Normal file
38
src/styles/components/cookie-consent.css
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
.cookie-consent {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(51, 51, 51, 0.95);
|
||||||
|
color: #fff;
|
||||||
|
padding: 15px;
|
||||||
|
z-index: 9999;
|
||||||
|
box-shadow: 0 -2px 10px rgba(0,0,0,0.2);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-consent-content {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
font-size: .9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-consent-button {
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-consent-button:hover {
|
||||||
|
background: #72757c;
|
||||||
|
}
|
||||||
110
src/styles/components/main-post-widget.css
Normal file
110
src/styles/components/main-post-widget.css
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/* ОСНОВНОЕ: ограничиваем ширину виджета */
|
||||||
|
.main-post-widget {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-link {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-link:hover .post-image {
|
||||||
|
transform: scale(1.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(135deg, #f5f5f5, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 16px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.625rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: white;
|
||||||
|
z-index: 2;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-overlay {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 24px;
|
||||||
|
background: linear-gradient(transparent, rgba(0,0,0,0.7) 70%);
|
||||||
|
color: white;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-overlay {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-overlay {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-link {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-link:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-overlay {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Адаптивность */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.main-post-widget {
|
||||||
|
border-radius: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-overlay {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-overlay {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/styles/components/mainline.css
Normal file
110
src/styles/components/mainline.css
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/* Основной контейнер */
|
||||||
|
.three-col-block {
|
||||||
|
display: flex;
|
||||||
|
margin: 30px 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: stretch; /* ВАЖНО: растягиваем все колонки на всю высоту */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ЛЕВАЯ КОЛОНКА - фиксированная */
|
||||||
|
.left-col {
|
||||||
|
flex: 0 0 260px;
|
||||||
|
min-width: 260px;
|
||||||
|
max-width: 260px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex; /* Добавляем flex для растягивания содержимого */
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-col > :deep(*) {
|
||||||
|
width: 100%; /* Растягиваем компонент на всю ширину */
|
||||||
|
height: 100%; /* Растягиваем компонент на всю высоту */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ЦЕНТРАЛЬНАЯ КОЛОНКА - гибкая */
|
||||||
|
.center-col {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
margin: 0 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-top, .center-bottom {
|
||||||
|
#flex: 1 1 0; /* ВАЖНО: оба блока занимают равную высоту */
|
||||||
|
background: #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
word-wrap: break-word;
|
||||||
|
display: flex; /* Для растягивания внутреннего содержимого */
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-top > :deep(*), .center-bottom > :deep(*) {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1; /* Растягиваем компонент на всю доступную высоту */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ПРАВАЯ КОЛОНКА - фиксированная */
|
||||||
|
.right-col {
|
||||||
|
flex: 0 0 260px;
|
||||||
|
min-width: 260px;
|
||||||
|
max-width: 260px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex; /* Добавляем flex для растягивания содержимого */
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-col > :deep(*) {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1; /* Растягиваем контент на всю высоту */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ограничиваем контент внутри центральной колонки */
|
||||||
|
.center-col > * {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* МОБИЛЬНАЯ ВЕРСИЯ */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.three-col-block {
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-col, .center-col, .right-col {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-col {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-col{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Сбрасываем flex свойства для мобильной версии */
|
||||||
|
.left-col, .right-col {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-top, .center-bottom {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
108
src/styles/embedded-content.css
Normal file
108
src/styles/embedded-content.css
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/* Добавьте это в ваш общий CSS файл */
|
||||||
|
|
||||||
|
/* Анимация загрузки */
|
||||||
|
@keyframes embedded-loading {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.embedded-loading {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: embedded-loading 1.5s infinite;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ошибка */
|
||||||
|
.embedded-error {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
background: #fff3f3;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #d32f2f;
|
||||||
|
border: 1px solid #ffcdd2;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embedded-error-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embedded-error-hint {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Карточка поста */
|
||||||
|
.embedded-post-card {
|
||||||
|
margin: 8px 0 8px 8px;
|
||||||
|
max-width: 267px;
|
||||||
|
background: #ececec;
|
||||||
|
float: right;
|
||||||
|
border-top: 6px solid #000; /* Жирный черный border только сверху */
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-family: Roboto, sans-serif;
|
||||||
|
padding: 1.2rem 0 1.1875rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embedded-post-card a {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embedded-post-image-container {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f5f5;
|
||||||
|
margin: 1.1875rem 0 1rem 0; /* Отступ сверху и снизу от фото */
|
||||||
|
}
|
||||||
|
|
||||||
|
.embedded-post-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embedded-post-content {
|
||||||
|
padding: 0 1.1875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embedded-post-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-family: Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Медиа-запрос для мобильных устройств */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.embedded-post-card {
|
||||||
|
float: none;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 20px 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Если нужно больше отступа для текста при обтекании */
|
||||||
|
.clearfix::after {
|
||||||
|
content: "";
|
||||||
|
clear: both;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
219
src/styles/footer.css
Normal file
219
src/styles/footer.css
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
.footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: #303030;
|
||||||
|
border-top: 2px solid #404040;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 100;
|
||||||
|
box-shadow: 0 -2px 15px rgba(0, 0, 0, 0.3);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Свернутое состояние */
|
||||||
|
.footer__collapsed {
|
||||||
|
padding: 12px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 16px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__toggle:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__publication-name {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__arrow {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
color: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* При развернутом состоянии стрелка поворачивается на 180° (вниз) */
|
||||||
|
.footer__toggle[aria-expanded="true"] .footer__arrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__copyright-collapsed {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #cccccc;
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Раскрытое состояние */
|
||||||
|
.footer__expanded {
|
||||||
|
max-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
padding: 0 20px;
|
||||||
|
background: #303030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__expanded:not([hidden]) {
|
||||||
|
max-height: 400px;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 20px;
|
||||||
|
border-top: 1px solid #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__menu {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__menu-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__menu-item {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__menu-link {
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__menu-link:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__copyright-expanded {
|
||||||
|
color: #cccccc;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
max-width: 800px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__copyright-expanded p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__expanded-bottom {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__collapse-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #ffffff;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__collapse-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-conf-docs{
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__age {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #000000;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 2px solid #404040;
|
||||||
|
margin-left: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Адаптивность */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.footer__toggle {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__publication-name,
|
||||||
|
.footer__copyright-collapsed {
|
||||||
|
margin: 4px 0;
|
||||||
|
text-align: center;
|
||||||
|
flex: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__menu-list {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__copyright-expanded {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Анимация для стрелки */
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer__toggle:hover .footer__arrow {
|
||||||
|
animation: bounce 0.5s ease;
|
||||||
|
}
|
||||||
@@ -1,11 +1,3 @@
|
|||||||
@import './reset.css';
|
|
||||||
@import './ContentLayout.css';
|
|
||||||
@import './article.css';
|
|
||||||
@import './embedded-content.css';
|
|
||||||
@import './components/ContentGrid.css';
|
|
||||||
@import './components/theme-colors.css';
|
|
||||||
|
|
||||||
|
|
||||||
html{
|
html{
|
||||||
font-family: Roboto,sans-serif;
|
font-family: Roboto,sans-serif;
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
|
|||||||
51
src/styles/header.css
Normal file
51
src/styles/header.css
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
.top-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__subtitle {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 42px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__subtitle::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: -15px;
|
||||||
|
width: 3px;
|
||||||
|
height: 80%;
|
||||||
|
border-left: 3px solid;
|
||||||
|
transform: translate(0, -40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__news-badge {
|
||||||
|
color: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__news-badge::after {
|
||||||
|
content: '|';
|
||||||
|
position: absolute;
|
||||||
|
right: -2px;
|
||||||
|
color: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__subtitle a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__subtitle a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
15
src/styles/main.css
Normal file
15
src/styles/main.css
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@import './reset.css';
|
||||||
|
@import './global.css';
|
||||||
|
@import './ContentLayout.css';
|
||||||
|
@import './header.css';
|
||||||
|
@import './mainmenu.css';
|
||||||
|
@import './mainmenu.css';
|
||||||
|
@import './components/mainline.css';
|
||||||
|
@import './components/colon-post.css';
|
||||||
|
@import './components/main-post-widget.css';
|
||||||
|
@import './footer.css';
|
||||||
|
@import './embedded-content.css';
|
||||||
|
@import './components/ContentGrid.css';
|
||||||
|
@import './components/theme-colors.css';
|
||||||
|
@import './components/RelatedPosts.css';
|
||||||
|
@import './components/cookie-consent.css';
|
||||||
142
src/styles/mainmenu.css
Normal file
142
src/styles/mainmenu.css
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
.primary-nav {
|
||||||
|
margin: 12px 0;
|
||||||
|
border-top: 1px solid black;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
background-color: white;
|
||||||
|
width: 100%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Стили только для десктопа */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.primary-nav.fixed {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Добавляем отступ для контента, когда меню фиксированное */
|
||||||
|
.primary-nav.fixed + * {
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Обертка для центрирования контента */
|
||||||
|
.primary-nav__wrapper {
|
||||||
|
width: 100%;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* При фиксированном меню обертка тоже фиксируется */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.primary-nav.fixed .primary-nav__wrapper {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 20px; /* Добавляем отступы по бокам */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-nav__content {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
min-height: 48px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Для десктопа добавляем ограничение ширины */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.primary-nav__content {
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Стили для логотипа, появляющегося при скролле */
|
||||||
|
.primary-nav__logo-scroll {
|
||||||
|
display: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
margin-right: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Показываем логотип только когда меню фиксированное и на десктопе */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.primary-nav.fixed .primary-nav__logo-scroll {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 1;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-nav__logo-image {
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
height: 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-nav__burger {
|
||||||
|
width: 60px;
|
||||||
|
height: 48px;
|
||||||
|
border-right: 1px solid silver;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
background-image: url('data:image/svg+xml;utf8,<svg width="23" height="16" viewBox="0 0 23 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0 0H23V2H0V0Z" fill="%23000000"/><path d="M0 7H23V9H0V7Z" fill="%23000000"/><path d="M0 14H23V16H0V14Z" fill="%23000000"/></svg>');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-size: 23px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-nav__burger:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-nav__list {
|
||||||
|
display: none;
|
||||||
|
flex: 1;
|
||||||
|
font-size: .875rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
gap: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-nav__item {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-nav__link {
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 1rem;
|
||||||
|
position: relative;
|
||||||
|
border-top: 1px solid currentColor;
|
||||||
|
transition: padding-top 0.2s ease, border-top-width 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-nav__link:hover {
|
||||||
|
border-top-width: 4px;
|
||||||
|
padding-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.primary-nav__list {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user