core-maugli 1.2.13 → 1.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/ArticleMeta.astro +2 -2
- package/src/components/Avatar.astro +7 -1
- package/src/components/Breadcrumbs.astro +1 -1
- package/src/components/Card.astro +40 -3
- package/src/components/Footer.astro +1 -1
- package/src/components/Header.astro +9 -1
- package/src/components/HeroImage.astro +26 -6
- package/src/components/Image.astro +29 -9
- package/src/components/LanguageSwitcher.astro +20 -2
- package/src/components/Nav.astro +9 -1
- package/src/components/NetlifyButton.astro +1 -1
- package/src/components/ProductBannerCard.astro +7 -1
- package/src/components/RubricCard.astro +50 -44
- package/src/components/TagsSection.astro +2 -0
- package/src/data/site-config.ts +15 -5
package/package.json
CHANGED
@@ -72,12 +72,12 @@ let authorImg = authorData.data.avatar || '/img/default/autor_default.webp';
|
|
72
72
|
data-astro-reload
|
73
73
|
style="text-decoration: none; color: inherit; z-index: 10; position: relative;"
|
74
74
|
>
|
75
|
-
<img src={authorImg} alt={authorName} class="w-8 h-8 rounded-full" />
|
75
|
+
<img src={authorImg} alt={authorName} class="w-8 h-8 rounded-full" loading="lazy" decoding="async" />
|
76
76
|
<span class="font-medium hover:text-[var(--brand-color)] transition-colors duration-200">{authorName}</span>
|
77
77
|
</a>
|
78
78
|
) : (
|
79
79
|
<>
|
80
|
-
<img src={authorImg} alt={authorName} class="w-8 h-8 rounded-full" />
|
80
|
+
<img src={authorImg} alt={authorName} class="w-8 h-8 rounded-full" loading="lazy" decoding="async" />
|
81
81
|
<span class="font-medium">{authorName}</span>
|
82
82
|
</>
|
83
83
|
)
|
@@ -13,7 +13,13 @@ const avatarSize = typeof size === 'number' ? `${size}px` : size;
|
|
13
13
|
---
|
14
14
|
|
15
15
|
<div class:list={['avatar-container', className]} style={`width: ${avatarSize}; height: ${avatarSize};`}>
|
16
|
-
<img
|
16
|
+
<img
|
17
|
+
src={src || '/img/default/autor_default.webp'}
|
18
|
+
alt={alt}
|
19
|
+
loading="lazy"
|
20
|
+
decoding="async"
|
21
|
+
class="w-full h-full object-cover"
|
22
|
+
/>
|
17
23
|
</div>
|
18
24
|
|
19
25
|
<style>
|
@@ -61,7 +61,7 @@ const isTagPage = currentUrl.startsWith('/tags/') && pathParts.length === 1;
|
|
61
61
|
idx === 0 ? (
|
62
62
|
<a href={crumb.href} class="flex-shrink-0" target="_blank" rel="noopener noreferrer">
|
63
63
|
<picture>
|
64
|
-
<img src={crumb.icon} alt="Maugli" class="w-9 h-9" />
|
64
|
+
<img src={crumb.icon} alt="Maugli" class="w-9 h-9" loading="lazy" decoding="async" />
|
65
65
|
</picture>
|
66
66
|
</a>
|
67
67
|
) : (
|
@@ -1,4 +1,7 @@
|
|
1
1
|
---
|
2
|
+
import fs from 'fs';
|
3
|
+
import path from 'path';
|
4
|
+
import { fileURLToPath } from 'url';
|
2
5
|
import { maugliConfig } from '../config/maugli.config';
|
3
6
|
import FormattedDate from './FormattedDate.astro';
|
4
7
|
|
@@ -20,8 +23,8 @@ const { href, title, image, seo, publishDate, excerpt, description, headingLevel
|
|
20
23
|
|
21
24
|
const TitleTag = headingLevel;
|
22
25
|
|
23
|
-
// Определяем изображение для карточки
|
24
|
-
const
|
26
|
+
// Определяем базовое изображение для карточки
|
27
|
+
const baseImage =
|
25
28
|
seo?.image?.src ||
|
26
29
|
(image && typeof image.src === 'string' && image.src.length > 0 ? image.src : undefined) ||
|
27
30
|
(type === 'blog'
|
@@ -31,8 +34,38 @@ const cardImage =
|
|
31
34
|
: type === 'product'
|
32
35
|
? maugliConfig.defaultProductImage
|
33
36
|
: maugliConfig.seo.defaultImage);
|
37
|
+
|
38
|
+
// Генерируем адаптивные версии изображений
|
39
|
+
function getResponsiveImages(imagePath: string) {
|
40
|
+
const basePath = imagePath.replace(/\.(webp|jpg|jpeg|png)$/i, '');
|
41
|
+
const extension = imagePath.match(/\.(webp|jpg|jpeg|png)$/i)?.[0] || '.webp';
|
42
|
+
|
43
|
+
return {
|
44
|
+
src: imagePath,
|
45
|
+
srcset: [`${basePath}-400${extension} 400w`, `${basePath}-800${extension} 800w`, `${basePath}-1200${extension} 1200w`, `${imagePath} 1200w`].join(', '),
|
46
|
+
sizes: '(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 400px'
|
47
|
+
};
|
48
|
+
}
|
49
|
+
|
50
|
+
const cardImage = getResponsiveImages(baseImage);
|
34
51
|
const cardImageAlt = seo?.image?.alt || image?.alt || title || 'Изображение';
|
35
52
|
|
53
|
+
// Используем уменьшенное превью, если оно существует
|
54
|
+
let previewImage;
|
55
|
+
if (cardImage) {
|
56
|
+
previewImage = cardImage.replace(/\/([^\/]+)$/, '/previews/$1');
|
57
|
+
|
58
|
+
const __filename = fileURLToPath(import.meta.url);
|
59
|
+
const projectRoot = path.resolve(path.dirname(__filename), '../..');
|
60
|
+
const previewFilePath = path.join(projectRoot, 'public', previewImage.replace(/^\//, ''));
|
61
|
+
|
62
|
+
if (!fs.existsSync(previewFilePath)) {
|
63
|
+
previewImage = undefined;
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
const finalImage = previewImage || cardImage;
|
68
|
+
|
36
69
|
// Определяем контент для отображения
|
37
70
|
const content = excerpt || description;
|
38
71
|
---
|
@@ -48,9 +81,13 @@ const content = excerpt || description;
|
|
48
81
|
<!-- Изображение -->
|
49
82
|
<div class="w-full aspect-[1200/630] bg-[var(--bg-muted)] overflow-hidden relative">
|
50
83
|
<img
|
51
|
-
src={cardImage}
|
84
|
+
src={cardImage.src}
|
85
|
+
srcset={cardImage.srcset}
|
86
|
+
sizes={cardImage.sizes}
|
52
87
|
alt={cardImageAlt}
|
53
88
|
loading="lazy"
|
89
|
+
width="1200"
|
90
|
+
height="630"
|
54
91
|
class="w-full h-full object-cover rounded-custom transition-transform duration-300 group-hover:scale-105"
|
55
92
|
/>
|
56
93
|
|
@@ -109,7 +109,7 @@ const copyrightYears = yearStart === yearEnd ? `${yearStart}` : `${yearStart}–
|
|
109
109
|
]}
|
110
110
|
>
|
111
111
|
<div class="flex flex-row justify-between items-end w-full mt-2">
|
112
|
-
<img src="/footerlabel.svg" alt="Maugli Label" style="height:32px;width:auto;" />
|
112
|
+
<img src="/footerlabel.svg" alt="Maugli Label" style="height:32px;width:auto;" loading="lazy" decoding="async" />
|
113
113
|
{
|
114
114
|
Object.values(maugliConfig.links || {}).some(Boolean) && (
|
115
115
|
<div class="flex flex-row items-center gap-2 ml-4">
|
@@ -6,7 +6,15 @@ import siteConfig from '../data/site-config';
|
|
6
6
|
{
|
7
7
|
siteConfig.logo && siteConfig.logo?.src ? (
|
8
8
|
<a href="/">
|
9
|
-
<img
|
9
|
+
<img
|
10
|
+
src={siteConfig.logo.src}
|
11
|
+
alt={siteConfig.logo.alt || ''}
|
12
|
+
width={siteConfig.logo.width}
|
13
|
+
height={siteConfig.logo.height}
|
14
|
+
loading="eager"
|
15
|
+
decoding="async"
|
16
|
+
class="max-h-12"
|
17
|
+
/>
|
10
18
|
</a>
|
11
19
|
) : (
|
12
20
|
<a class="font-serif text-2xl leading-tight font-medium text-theme-foreground sm:text-4xl" href="/">
|
@@ -15,15 +15,37 @@ const { src, alt = '', caption, width = defaultWidth, height, className = '', sr
|
|
15
15
|
// Вариант aspect-ratio: если есть оба размера, рассчитываем; иначе не пишем!
|
16
16
|
const aspectRatio = width && height ? `${width} / ${height}` : undefined;
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
// Генерируем адаптивные версии изображений
|
19
|
+
function getResponsiveImages(imagePath: string) {
|
20
|
+
const basePath = imagePath.replace(/\.(webp|jpg|jpeg|png)$/i, '');
|
21
|
+
const extension = imagePath.match(/\.(webp|jpg|jpeg|png)$/i)?.[0] || '.webp';
|
22
|
+
|
23
|
+
return {
|
24
|
+
src: imagePath,
|
25
|
+
srcset: [`${basePath}-400${extension} 400w`, `${basePath}-800${extension} 800w`, `${basePath}-1200${extension} 1200w`, `${imagePath} 1200w`].join(', '),
|
26
|
+
sizes: '(max-width: 768px) 100vw, (max-width: 1024px) 80vw, 1200px'
|
27
|
+
};
|
28
|
+
}
|
29
|
+
|
30
|
+
let imageData;
|
31
|
+
if (srcset) {
|
32
|
+
// Если передан готовый srcset, используем его
|
33
|
+
imageData = {
|
34
|
+
src: src,
|
35
|
+
srcset: srcset,
|
36
|
+
sizes: '100vw'
|
37
|
+
};
|
38
|
+
} else {
|
39
|
+
// Генерируем адаптивные изображения
|
40
|
+
imageData = getResponsiveImages(src);
|
21
41
|
}
|
22
42
|
---
|
23
43
|
|
24
44
|
<figure class={`w-full max-w-[${width}px] mx-auto rounded-custom overflow-hidden bg-[var(--bg-muted)] ${className}`}>
|
25
45
|
<img
|
26
|
-
src={src}
|
46
|
+
src={imageData.src}
|
47
|
+
srcset={imageData.srcset}
|
48
|
+
sizes={imageData.sizes}
|
27
49
|
alt={alt}
|
28
50
|
width={width}
|
29
51
|
height={height}
|
@@ -31,8 +53,6 @@ if (!srcset && src.includes('default-')) {
|
|
31
53
|
class="block rounded-custom object-cover"
|
32
54
|
loading="eager"
|
33
55
|
decoding="async"
|
34
|
-
srcset={realSrcset}
|
35
|
-
sizes="100vw"
|
36
56
|
/>
|
37
57
|
{caption && <figcaption class="mt-2 text-sm text-[var(--text-main)] opacity-80 text-center px-2">{caption}</figcaption>}
|
38
58
|
</figure>
|
@@ -1,13 +1,33 @@
|
|
1
1
|
---
|
2
|
-
export
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
export interface Props {
|
3
|
+
src: string;
|
4
|
+
alt?: string;
|
5
|
+
width?: number | string;
|
6
|
+
height?: number | string;
|
7
|
+
className?: string;
|
8
|
+
loading?: 'lazy' | 'eager';
|
9
|
+
style?: string;
|
10
|
+
}
|
11
|
+
|
12
|
+
const {
|
13
|
+
src,
|
14
|
+
alt = '',
|
15
|
+
width,
|
16
|
+
height,
|
17
|
+
className = '',
|
18
|
+
loading = 'lazy',
|
19
|
+
style = ''
|
20
|
+
} = Astro.props as Props;
|
9
21
|
---
|
10
22
|
|
11
|
-
|
23
|
+
<img
|
24
|
+
src={src}
|
25
|
+
alt={alt}
|
26
|
+
width={width}
|
27
|
+
height={height}
|
28
|
+
class={className}
|
29
|
+
loading={loading}
|
30
|
+
decoding="async"
|
31
|
+
style={style}
|
32
|
+
/>
|
12
33
|
|
13
|
-
<img src={src} alt={alt} width={width} height={height} class={className} loading={loading} style={style} />
|
@@ -24,7 +24,17 @@ const current = availableLanguages.find((l) => l.code === currentLang) || availa
|
|
24
24
|
class="flag-box flex items-center justify-center w-7 h-7 rounded-[var(--radius-main)] shadow-[var(--shadow-main)]"
|
25
25
|
style="min-width:28px; min-height:28px; max-width:28px; max-height:28px;"
|
26
26
|
>
|
27
|
-
{current &&
|
27
|
+
{current && (
|
28
|
+
<img
|
29
|
+
src={current.icon}
|
30
|
+
alt={current.code.toUpperCase()}
|
31
|
+
width="28"
|
32
|
+
height="28"
|
33
|
+
style="pointer-events:none;"
|
34
|
+
loading="lazy"
|
35
|
+
decoding="async"
|
36
|
+
/>
|
37
|
+
)}
|
28
38
|
</span>
|
29
39
|
<svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
|
30
40
|
<path
|
@@ -57,7 +67,15 @@ const current = availableLanguages.find((l) => l.code === currentLang) || availa
|
|
57
67
|
class="flag-box flex items-center justify-center w-7 h-7 rounded-[var(--radius-main)] shadow-[var(--shadow-main)]"
|
58
68
|
style="min-width:28px; min-height:28px; max-width:28px; max-height:28px;"
|
59
69
|
>
|
60
|
-
<img
|
70
|
+
<img
|
71
|
+
src={lang.icon}
|
72
|
+
alt={lang.code.toUpperCase()}
|
73
|
+
width="28"
|
74
|
+
height="28"
|
75
|
+
style="pointer-events:none;"
|
76
|
+
loading="lazy"
|
77
|
+
decoding="async"
|
78
|
+
/>
|
61
79
|
</span>
|
62
80
|
<span class="text-heading">{lang.label}</span>
|
63
81
|
</a>
|
package/src/components/Nav.astro
CHANGED
@@ -24,7 +24,15 @@ const navLinks = (maugliConfig.navLinks || []).map((link) => {
|
|
24
24
|
<a href={maugliConfig.brand.logoHref && maugliConfig.brand.logoHref.trim() ? maugliConfig.brand.logoHref : '/'} class="ml-6 flex-shrink-0 card-blur">
|
25
25
|
<picture>
|
26
26
|
<source srcset={maugliConfig.brand.logoDark} media="(prefers-color-scheme: dark)" />
|
27
|
-
<img
|
27
|
+
<img
|
28
|
+
src={maugliConfig.brand.logoLight}
|
29
|
+
alt="Логотип"
|
30
|
+
width="48"
|
31
|
+
height="48"
|
32
|
+
class="w-12 h-12"
|
33
|
+
loading="eager"
|
34
|
+
decoding="async"
|
35
|
+
/>
|
28
36
|
</picture>
|
29
37
|
</a>
|
30
38
|
<ul
|
@@ -16,7 +16,7 @@ const netlifyUrl = `https://app.netlify.com/start/deploy?repository=${repository
|
|
16
16
|
{
|
17
17
|
netlifyEnabled && (
|
18
18
|
<a href={netlifyUrl} target="_blank" rel="noopener noreferrer" class={className}>
|
19
|
-
<img src="https://www.netlify.com/img/deploy/button.svg" alt="Deploy to Netlify" />
|
19
|
+
<img src="https://www.netlify.com/img/deploy/button.svg" alt="Deploy to Netlify" loading="lazy" decoding="async" />
|
20
20
|
</a>
|
21
21
|
)
|
22
22
|
}
|
@@ -24,7 +24,13 @@ const buttons = (dict as any).buttons || (fallbackDict as any).buttons || {};
|
|
24
24
|
---
|
25
25
|
|
26
26
|
<div class="product-banner-card relative aspect-[1200/630] overflow-hidden bg-[var(--card-bg)] rounded-custom flex flex-col justify-end">
|
27
|
-
<img
|
27
|
+
<img
|
28
|
+
src={bannerImage}
|
29
|
+
alt={seo?.image?.alt || image?.alt || title || 'Product'}
|
30
|
+
class="absolute inset-0 w-full h-full object-cover rounded-custom z-0"
|
31
|
+
loading="lazy"
|
32
|
+
decoding="async"
|
33
|
+
/>
|
28
34
|
{
|
29
35
|
buttonHref ? (
|
30
36
|
<a
|
@@ -112,8 +112,8 @@ function getPreviewImageSrc(imageSrc: string): string {
|
|
112
112
|
return absolutePreviewPath;
|
113
113
|
}
|
114
114
|
|
115
|
-
//
|
116
|
-
const previewImageSrc =
|
115
|
+
// Если указано своё изображение — показываем его превью, иначе дефолтное
|
116
|
+
const previewImageSrc = getPreviewImageSrc(image?.src || maugliConfig.defaultRubricImage);
|
117
117
|
|
118
118
|
// Список стоп-слов (предлоги, союзы, знаки)
|
119
119
|
const stopWords = [
|
@@ -194,52 +194,58 @@ if (import.meta.env.DEV) {
|
|
194
194
|
class={`w-full h-[195px] border border-[var(--border-main)] rounded-custom card-bg hover:card-shadow hover:-translate-y-1 transition-all duration-300 p-6 flex flex-row gap-4 items-start ${className}`}
|
195
195
|
>
|
196
196
|
<!-- Левая часть: картинка и дата -->
|
197
|
-
|
198
|
-
|
199
|
-
{
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
197
|
+
<<<<<<< HEAD
|
198
|
+
<div class="flex flex-col items-end gap-2 w-[105px] h-[147px]">
|
199
|
+
<img src={previewImageSrc} alt={image?.alt || title} class="w-[105px] h-[107px] object-cover rounded-custom" loading="lazy" decoding="async" />
|
200
|
+
=======
|
201
|
+
<div class="flex flex-col items-end gap-2 w-[105px] h-[147px] relative">
|
202
|
+
<img src={previewImageSrc} alt={title} class="w-[105px] h-[107px] object-cover rounded-custom" loading="lazy" />
|
203
|
+
{
|
204
|
+
rubricInitials && (
|
205
|
+
<span style="position:absolute;left:0;top:0;width:105px;height:107px;display:flex;align-items:center;justify-content:center;pointer-events:none;z-index:2;">
|
206
|
+
<span style="position:absolute;left:0;top:0;width:105px;height:107px;background:rgba(0,0,0,0.10);border-radius:inherit;z-index:1;" />
|
207
|
+
<span style="position:relative;z-index:2;font-size:48px;font-weight:700;font-family:inherit;color:var(--brand-color);letter-spacing:2px;">
|
208
|
+
{rubricInitials}
|
209
|
+
</span>
|
205
210
|
</span>
|
211
|
+
)
|
212
|
+
}
|
213
|
+
>>>>>>> 41e6ca2 (fix: always use default rubric image from config, initials overlay, preview logic)
|
214
|
+
<div class="flex flex-col items-end gap-1 w-[74px] h-[32px]">
|
215
|
+
<span class={`flex items-center gap-1 text-[12px] text-right ${isBrandDate ? 'text-[var(--brand-color)]' : 'text-[var(--text-muted)]'}`}>
|
216
|
+
<svg
|
217
|
+
width="16"
|
218
|
+
height="16"
|
219
|
+
viewBox="0 0 24 24"
|
220
|
+
fill="none"
|
221
|
+
xmlns="http://www.w3.org/2000/svg"
|
222
|
+
style="display:inline-block;vertical-align:middle;opacity:0.6; color: var(--text-muted);"
|
223
|
+
>
|
224
|
+
<path
|
225
|
+
d="M17.7909 6.12232C14.9505 3.32362 10.5815 3.00989 7.39551 5.15377L7.38659 3.92302C7.38509 3.71587 7.21589 3.54914 7.00866 3.55079L6.25874 3.55664C6.05166 3.55822 5.88516 3.72734 5.88666 3.93434L5.90736 6.74122C5.91029 7.15357 6.24576 7.48537 6.65736 7.48537C6.65886 7.48537 6.66104 7.48537 6.66321 7.48537L9.47046 7.46467C9.67761 7.46317 9.84426 7.29389 9.84269 7.08674L9.83684 6.33667C9.83526 6.12959 9.66614 5.96309 9.45914 5.96459L8.98199 5.96804C11.4928 4.71464 14.6299 5.11372 16.7377 7.19017C18.7606 9.18427 19.3134 12.182 18.1639 14.7525C18.082 14.9355 18.1491 15.1487 18.3276 15.24L18.997 15.582C19.1866 15.6789 19.4265 15.6008 19.5145 15.4069C20.9445 12.2567 20.2743 8.57039 17.7909 6.12232ZM17.3434 16.5132C17.3419 16.5132 17.3397 16.5132 17.3375 16.5132L14.5303 16.5338C14.3231 16.5354 14.1565 16.7046 14.158 16.9117L14.1639 17.6618C14.1655 17.8688 14.3346 18.0353 14.5416 18.0339L15.0183 18.0304C12.5073 19.2835 9.37079 18.8841 7.26299 16.8083C5.24009 14.8142 4.68734 11.8164 5.83686 9.24599C5.91869 9.06299 5.85164 8.84977 5.67314 8.75849L5.00376 8.41649C4.81409 8.31959 4.57424 8.39767 4.48619 8.59154C3.05609 11.7417 3.72636 15.428 6.20969 17.8762C7.81439 19.4575 9.90771 20.2456 11.9995 20.2456C13.6101 20.2456 15.2191 19.7767 16.605 18.8438L16.6139 20.0754C16.6154 20.2825 16.7846 20.4493 16.9918 20.4477L17.7418 20.4418C17.9488 20.4402 18.1153 20.2711 18.1138 20.0641L18.0931 17.2573C18.0904 16.8449 17.755 16.5132 17.3434 16.5132Z"
|
226
|
+
fill="currentColor"></path>
|
227
|
+
</svg>
|
228
|
+
{formattedDate}
|
206
229
|
</span>
|
207
|
-
|
208
|
-
}
|
209
|
-
<div class="flex flex-col items-end gap-1 w-[74px] h-[32px]">
|
210
|
-
<span class={`flex items-center gap-1 text-[12px] text-right ${isBrandDate ? 'text-[var(--brand-color)]' : 'text-[var(--text-muted)]'}`}>
|
211
|
-
<svg
|
212
|
-
width="16"
|
213
|
-
height="16"
|
214
|
-
viewBox="0 0 24 24"
|
215
|
-
fill="none"
|
216
|
-
xmlns="http://www.w3.org/2000/svg"
|
217
|
-
style="display:inline-block;vertical-align:middle;opacity:0.6; color: var(--text-muted);"
|
218
|
-
>
|
219
|
-
<path
|
220
|
-
d="M17.7909 6.12232C14.9505 3.32362 10.5815 3.00989 7.39551 5.15377L7.38659 3.92302C7.38509 3.71587 7.21589 3.54914 7.00866 3.55079L6.25874 3.55664C6.05166 3.55822 5.88516 3.72734 5.88666 3.93434L5.90736 6.74122C5.91029 7.15357 6.24576 7.48537 6.65736 7.48537C6.65886 7.48537 6.66104 7.48537 6.66321 7.48537L9.47046 7.46467C9.67761 7.46317 9.84426 7.29389 9.84269 7.08674L9.83684 6.33667C9.83526 6.12959 9.66614 5.96309 9.45914 5.96459L8.98199 5.96804C11.4928 4.71464 14.6299 5.11372 16.7377 7.19017C18.7606 9.18427 19.3134 12.182 18.1639 14.7525C18.082 14.9355 18.1491 15.1487 18.3276 15.24L18.997 15.582C19.1866 15.6789 19.4265 15.6008 19.5145 15.4069C20.9445 12.2567 20.2743 8.57039 17.7909 6.12232ZM17.3434 16.5132C17.3419 16.5132 17.3397 16.5132 17.3375 16.5132L14.5303 16.5338C14.3231 16.5354 14.1565 16.7046 14.158 16.9117L14.1639 17.6618C14.1655 17.8688 14.3346 18.0353 14.5416 18.0339L15.0183 18.0304C12.5073 19.2835 9.37079 18.8841 7.26299 16.8083C5.24009 14.8142 4.68734 11.8164 5.83686 9.24599C5.91869 9.06299 5.85164 8.84977 5.67314 8.75849L5.00376 8.41649C4.81409 8.31959 4.57424 8.39767 4.48619 8.59154C3.05609 11.7417 3.72636 15.428 6.20969 17.8762C7.81439 19.4575 9.90771 20.2456 11.9995 20.2456C13.6101 20.2456 15.2191 19.7767 16.605 18.8438L16.6139 20.0754C16.6154 20.2825 16.7846 20.4493 16.9918 20.4477L17.7418 20.4418C17.9488 20.4402 18.1153 20.2711 18.1138 20.0641L18.0931 17.2573C18.0904 16.8449 17.755 16.5132 17.3434 16.5132Z"
|
221
|
-
fill="currentColor"></path>
|
222
|
-
</svg>
|
223
|
-
{formattedDate}
|
224
|
-
</span>
|
230
|
+
</div>
|
225
231
|
</div>
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
232
|
+
<!-- Правая часть: контент -->
|
233
|
+
<div class="flex flex-col justify-start items-start h-[147px] flex-1 min-w-0">
|
234
|
+
<div class="flex flex-row items-start gap-2 w-full">
|
235
|
+
<h3 class="font-serif font-[700] text-[22px] text-[var(--text-heading)] leading-[1] truncate">{title}</h3>
|
236
|
+
<CountBadge count={postCount} />
|
237
|
+
</div>
|
238
|
+
<div class="mt-2 text-[14px] text-[var(--text-main)] leading-[1.3] line-clamp-6 opacity-80">
|
239
|
+
{description}
|
240
|
+
</div>
|
235
241
|
</div>
|
236
242
|
</div>
|
237
243
|
</article>
|
238
|
-
</a>
|
239
244
|
|
240
|
-
<style>
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
</style>
|
245
|
+
<style>
|
246
|
+
article:hover {
|
247
|
+
box-shadow: var(--card-shadow);
|
248
|
+
}
|
249
|
+
/* Карточка всегда двухколоночная, контент сверху, текст description не прижат к низу */
|
250
|
+
</style>
|
251
|
+
</a>
|
@@ -43,6 +43,8 @@ const rubricImageFinal = rubricImage && rubricImage.length > 0 ? rubricImage : m
|
|
43
43
|
alt={rubricName || tagsSection.rubricAlt}
|
44
44
|
class="w-20 h-20 sm:w-40 sm:h-40 object-cover rounded-[16px_48px_16px_48px] border border-[var(--border-main)]"
|
45
45
|
style="flex-shrink:0;"
|
46
|
+
loading="lazy"
|
47
|
+
decoding="async"
|
46
48
|
/>
|
47
49
|
<div class="flex flex-col items-center sm:items-start text-center sm:text-left">
|
48
50
|
{rubricName && <h2 class="text-xl sm:text-2xl font-bold mb-2">{rubricName}</h2>}
|
package/src/data/site-config.ts
CHANGED
@@ -2,6 +2,8 @@ export type Image = {
|
|
2
2
|
src: string;
|
3
3
|
alt?: string;
|
4
4
|
caption?: string;
|
5
|
+
width?: number;
|
6
|
+
height?: number;
|
5
7
|
};
|
6
8
|
|
7
9
|
export type Link = {
|
@@ -59,6 +61,12 @@ export type SiteConfig = {
|
|
59
61
|
|
60
62
|
const siteConfig: SiteConfig = {
|
61
63
|
website: 'https://techrev.maugli.cfd',
|
64
|
+
logo: {
|
65
|
+
src: '/logo.svg',
|
66
|
+
alt: 'Maugli Content Farm',
|
67
|
+
width: 48,
|
68
|
+
height: 48
|
69
|
+
},
|
62
70
|
title: 'ТехРев',
|
63
71
|
subtitle: 'Блог об автоматизации с ИИ от ИИ-автора',
|
64
72
|
description: 'Создание контента для соцсетей и блогов быстро, дешево и качественно',
|
@@ -67,7 +75,7 @@ const siteConfig: SiteConfig = {
|
|
67
75
|
alt: 'Автоматизация и ИИ -- новая технологическая революция',
|
68
76
|
width: 1200,
|
69
77
|
height: 630,
|
70
|
-
caption: '...'
|
78
|
+
caption: '...'
|
71
79
|
},
|
72
80
|
author: {
|
73
81
|
name: 'ИльичAI',
|
@@ -77,14 +85,16 @@ const siteConfig: SiteConfig = {
|
|
77
85
|
description: 'AI-эксперт, созданный в Maugli Content Farm, работает на GPT4.1, редактор и аналитик.',
|
78
86
|
sameAs: [
|
79
87
|
'https://t.me/techrev_maugli',
|
80
|
-
'https://twitter.com/'
|
88
|
+
'https://twitter.com/' // другие профили по желанию
|
81
89
|
]
|
82
90
|
},
|
83
91
|
publisher: {
|
84
92
|
name: 'Maugli Content Farm',
|
85
93
|
logo: {
|
86
94
|
src: '/logo.svg',
|
87
|
-
alt: 'Maugli Content Farm'
|
95
|
+
alt: 'Maugli Content Farm',
|
96
|
+
width: 48,
|
97
|
+
height: 48
|
88
98
|
},
|
89
99
|
url: 'https://maugli.cfd',
|
90
100
|
type: 'Organization'
|
@@ -138,12 +148,12 @@ const siteConfig: SiteConfig = {
|
|
138
148
|
],
|
139
149
|
hero: {
|
140
150
|
title: 'ТехРев — блог, освобождающий людей от ручного труда',
|
141
|
-
text:
|
151
|
+
text: ' Канал ведет Ильич ИИ, заряженный верой в то, что может сделать жизнь людей проще',
|
142
152
|
image: {
|
143
153
|
src: '/hero.webp',
|
144
154
|
alt: 'Автор блога Ильич ИИ прямо здесь и сейчас творит технологическую революию',
|
145
155
|
width: 1200, // ← добавь
|
146
|
-
height: 630
|
156
|
+
height: 630
|
147
157
|
},
|
148
158
|
actions: [
|
149
159
|
{
|