core-maugli 1.0.2

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.
Files changed (206) hide show
  1. package/LICENSE +727 -0
  2. package/README.md +108 -0
  3. package/bin/index.js +9 -0
  4. package/bin/init.js +38 -0
  5. package/package.json +73 -0
  6. package/public/blackbox.webp +0 -0
  7. package/public/favicon.svg +10 -0
  8. package/public/footerlabel.svg +18 -0
  9. package/public/icon-192.png +0 -0
  10. package/public/icon-512.png +0 -0
  11. package/public/img/page-images/blog_default.webp +0 -0
  12. package/public/logo-icon.svg +3 -0
  13. package/public/logoblog-icon.svg +10 -0
  14. package/public/manifest.webmanifest +21 -0
  15. package/public/maugli_for_animation.svg +6 -0
  16. package/public/mauglilabel.svg +17 -0
  17. package/public/tr-about-1200.webp +0 -0
  18. package/public/tr-about-400.webp +0 -0
  19. package/public/tr-about-800.webp +0 -0
  20. package/public/tr-about.webp +0 -0
  21. package/public/tr-post-1-1200.webp +0 -0
  22. package/public/tr-post-1-400.webp +0 -0
  23. package/public/tr-post-1-800.webp +0 -0
  24. package/public/tr-post0-1200.webp +0 -0
  25. package/public/tr-post0-400.webp +0 -0
  26. package/public/tr-post0-800.webp +0 -0
  27. package/public/tr-post0.webp +0 -0
  28. package/public/tr-prewiew.webp +0 -0
  29. package/resize-all.cjs +29 -0
  30. package/scripts/featured.js +208 -0
  31. package/scripts/update-with-backup.js +34 -0
  32. package/scripts/upgrade-config.js +90 -0
  33. package/scripts/verify-assets.js +28 -0
  34. package/src/assets/img/default/autor_default.webp +0 -0
  35. package/src/assets/img/default/blog_default.webp +0 -0
  36. package/src/assets/img/default/product_default.webp +0 -0
  37. package/src/assets/img/default/project_default.webp +0 -0
  38. package/src/assets/img/default/rubric_default.webp +0 -0
  39. package/src/assets/img/examples/authors/anna.webp +0 -0
  40. package/src/assets/img/examples/authors/carlos.webp +0 -0
  41. package/src/assets/img/examples/authors/daria.webp +0 -0
  42. package/src/assets/img/examples/authors/dmitry.webp +0 -0
  43. package/src/assets/img/examples/authors/igor.webp +0 -0
  44. package/src/assets/img/examples/authors/john.webp +0 -0
  45. package/src/assets/img/examples/blog/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva.webp +0 -0
  46. package/src/assets/img/examples/blog/post_11.webp +0 -0
  47. package/src/assets/img/examples/blog/post_12.webp +0 -0
  48. package/src/assets/img/examples/blog/post_1_jsonld_guide.webp +0 -0
  49. package/src/assets/img/examples/blog/test-post.webp +0 -0
  50. package/src/assets/img/examples/blog/tr-post-1.webp +0 -0
  51. package/src/assets/img/examples/products/product_1.webp +0 -0
  52. package/src/assets/img/examples/products/product_2.webp +0 -0
  53. package/src/assets/img/examples/projects/project_1.webp +0 -0
  54. package/src/assets/img/examples/projects/project_2.webp +0 -0
  55. package/src/components/ArticleMeta.astro +103 -0
  56. package/src/components/AuthorCard.astro +95 -0
  57. package/src/components/AuthorLink.astro +25 -0
  58. package/src/components/AuthorLinksGroup.astro +135 -0
  59. package/src/components/Avatar.astro +32 -0
  60. package/src/components/BaseHead.astro +121 -0
  61. package/src/components/Breadcrumbs.astro +90 -0
  62. package/src/components/Button.astro +25 -0
  63. package/src/components/Card.astro +99 -0
  64. package/src/components/ContentFooter.astro +147 -0
  65. package/src/components/CopyLinkButton.astro +133 -0
  66. package/src/components/CountBadge.astro +17 -0
  67. package/src/components/Footer.astro +122 -0
  68. package/src/components/FormattedDate.astro +59 -0
  69. package/src/components/Header.astro +18 -0
  70. package/src/components/HeroImage.astro +38 -0
  71. package/src/components/IconButton.astro +23 -0
  72. package/src/components/Image.astro +13 -0
  73. package/src/components/InfoCard.astro +123 -0
  74. package/src/components/LanguageSwitcher.astro +122 -0
  75. package/src/components/MaugliFloatingLabel.astro +454 -0
  76. package/src/components/MobileTagsAndShare.astro +71 -0
  77. package/src/components/Nav.astro +151 -0
  78. package/src/components/NavLink.astro +31 -0
  79. package/src/components/Pagination.astro +70 -0
  80. package/src/components/PostPreview.astro +22 -0
  81. package/src/components/ProductBannerCard.astro +47 -0
  82. package/src/components/ProductPreview.astro +21 -0
  83. package/src/components/ProjectPreview.astro +21 -0
  84. package/src/components/RubricCard.astro +142 -0
  85. package/src/components/ShareIcon.astro +93 -0
  86. package/src/components/ShareLink.astro +58 -0
  87. package/src/components/Subscribe.astro +68 -0
  88. package/src/components/SummaryFAQCard.astro +106 -0
  89. package/src/components/TableOfContents.astro +143 -0
  90. package/src/components/TagPill.astro +41 -0
  91. package/src/components/TagPills.astro +42 -0
  92. package/src/components/TagsAndShare.astro +379 -0
  93. package/src/components/TagsSection.astro +203 -0
  94. package/src/components/ThemeToggle.astro +58 -0
  95. package/src/config/maugli.config.ts +213 -0
  96. package/src/content/authors/daria-zorina.md +42 -0
  97. package/src/content/authors/default-autor.md +47 -0
  98. package/src/content/authors/igor-sokolov.md +43 -0
  99. package/src/content/authors/john-walker.md +46 -0
  100. package/src/content/blog/jsonld-guide.md +260 -0
  101. package/src/content/blog/post-0.md +49 -0
  102. package/src/content/blog/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva.md +72 -0
  103. package/src/content/blog/post-agent-experience-mcp-biznes-v-epohu-ii-agentov.md +116 -0
  104. package/src/content/blog/test-post-2025-07-11.md +73 -0
  105. package/src/content/config.ts +80 -0
  106. package/src/content/pages/about.md +40 -0
  107. package/src/content/pages/about.mdx +27 -0
  108. package/src/content/pages/authors.mdx +49 -0
  109. package/src/content/pages/blog.mdx +31 -0
  110. package/src/content/pages/contact.md +10 -0
  111. package/src/content/pages/products.mdx +30 -0
  112. package/src/content/pages/projects.mdx +28 -0
  113. package/src/content/pages/rubrics.mdx +35 -0
  114. package/src/content/pages/terms.md +12 -0
  115. package/src/content/products/example-product.md +28 -0
  116. package/src/content/products/maugli-editor.md +35 -0
  117. package/src/content/products/maugli-freeblog.md +162 -0
  118. package/src/content/projects/example-project.md +28 -0
  119. package/src/content/projects/project-1.md +70 -0
  120. package/src/content/projects/project-2.md +33 -0
  121. package/src/content/tags/ai-business.mdx +18 -0
  122. package/src/content/tags/automation.mdx +18 -0
  123. package/src/content/tags/content-strategy.mdx +18 -0
  124. package/src/content/tags/growth-marketing.mdx +18 -0
  125. package/src/content/tags/industry-reviews.mdx +18 -0
  126. package/src/content/tags/interesting.mdx +18 -0
  127. package/src/content/tags/seo-ai-seo.mdx +18 -0
  128. package/src/content.config.ts +260 -0
  129. package/src/data/site-config.ts +164 -0
  130. package/src/i18n/de.json +126 -0
  131. package/src/i18n/en.json +126 -0
  132. package/src/i18n/es.json +126 -0
  133. package/src/i18n/fr.json +126 -0
  134. package/src/i18n/index.ts +10 -0
  135. package/src/i18n/ja.json +126 -0
  136. package/src/i18n/languages.ts +23 -0
  137. package/src/i18n/pt.json +126 -0
  138. package/src/i18n/ru.json +123 -0
  139. package/src/i18n/zh.json +126 -0
  140. package/src/icons/ArrowLeft.astro +13 -0
  141. package/src/icons/ArrowRight.astro +13 -0
  142. package/src/icons/flags/brazil.svg +14 -0
  143. package/src/icons/flags/china.svg +15 -0
  144. package/src/icons/flags/france.svg +12 -0
  145. package/src/icons/flags/germany.svg +12 -0
  146. package/src/icons/flags/japan.svg +11 -0
  147. package/src/icons/flags/russia.svg +12 -0
  148. package/src/icons/flags/spain.svg +12 -0
  149. package/src/icons/flags/united arab emirates.svg +13 -0
  150. package/src/icons/flags/united states.svg +15 -0
  151. package/src/icons/socials/BlueskyIcon.astro +9 -0
  152. package/src/icons/socials/EmailIcon.astro +8 -0
  153. package/src/icons/socials/LinkedinIcon.astro +9 -0
  154. package/src/icons/socials/MastodonIcon.astro +9 -0
  155. package/src/icons/socials/MediumIcon.astro +9 -0
  156. package/src/icons/socials/RedditIcon.astro +11 -0
  157. package/src/icons/socials/TelegramIcon.astro +11 -0
  158. package/src/icons/socials/TwitterIcon.astro +9 -0
  159. package/src/img/default/autor_default.webp +0 -0
  160. package/src/img/default/default.webp +0 -0
  161. package/src/img/default/rubric_default.webp +0 -0
  162. package/src/img/default/test.webp +0 -0
  163. package/src/img/default/test2.webp +0 -0
  164. package/src/img/examples/authors/anna.webp +0 -0
  165. package/src/img/examples/authors/carlos.webp +0 -0
  166. package/src/img/examples/authors/daria.webp +0 -0
  167. package/src/img/examples/authors/dmitry.webp +0 -0
  168. package/src/img/examples/authors/igor.webp +0 -0
  169. package/src/img/examples/authors/john.webp +0 -0
  170. package/src/img/examples/blog/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva.webp +0 -0
  171. package/src/img/examples/blog/post-2-avtomatizaciya-kontenta-kak-neiroseti-ubivayut-perfekcionizm-v-biznese.webp +0 -0
  172. package/src/img/examples/blog/post-3-laik-ne-valyuta-kak-avtomatizaciya-marketinga-spasaet-ot-lozhnyh-metrik.webp +0 -0
  173. package/src/img/examples/blog/post-5-5-fatalnyh-oshibok-marketinga-kotorye-ubivayut-startapy-na-starte.webp +0 -0
  174. package/src/img/examples/blog/post-6-5-strategii-kontent-marketinga-dlya-startapov-avtomatizaciya-i-revolyuciya.webp +0 -0
  175. package/src/img/examples/blog/post-7-viralnyi-kontent-ne-udacha-a-strategiya-avtomatizaciya-marketinga.webp +0 -0
  176. package/src/img/examples/blog/post-agent-experience-mcp-biznes-v-epohu-ii-agentov.webp +0 -0
  177. package/src/img/examples/blog/post_1_jsonld_guide.webp +0 -0
  178. package/src/img/examples/blog/tr-post-1.webp +0 -0
  179. package/src/layouts/BaseLayout.astro +59 -0
  180. package/src/pages/404.astro +24 -0
  181. package/src/pages/[...id].astro +50 -0
  182. package/src/pages/about.astro +0 -0
  183. package/src/pages/authors/[...page].astro +105 -0
  184. package/src/pages/authors/[id].astro +165 -0
  185. package/src/pages/blog/[...page].astro +59 -0
  186. package/src/pages/blog/[id].astro +175 -0
  187. package/src/pages/index.astro +90 -0
  188. package/src/pages/products/[...page].astro +50 -0
  189. package/src/pages/products/[id].astro +221 -0
  190. package/src/pages/projects/[...page].astro +74 -0
  191. package/src/pages/projects/[id].astro +165 -0
  192. package/src/pages/projects/tags/[id]/[...page].astro +58 -0
  193. package/src/pages/rss.xml.js +5 -0
  194. package/src/pages/tags/[id]/[...page].astro +110 -0
  195. package/src/pages/tags/index.astro +124 -0
  196. package/src/scripts/infoCardFadeIn.js +22 -0
  197. package/src/styles/global.css +272 -0
  198. package/src/utils/common-utils.ts +0 -0
  199. package/src/utils/content-loader.ts +14 -0
  200. package/src/utils/data-utils.ts +49 -0
  201. package/src/utils/featuredManager.ts +118 -0
  202. package/src/utils/posts.ts +43 -0
  203. package/src/utils/reading-time.ts +28 -0
  204. package/src/utils/remark-slugify.js +8 -0
  205. package/src/utils/rss.ts +23 -0
  206. package/typograf-batch.js +49 -0
@@ -0,0 +1,151 @@
1
+ ---
2
+ import NavLink from './NavLink.astro';
3
+
4
+ import ThemeToggle from './ThemeToggle.astro';
5
+ import LanguageSwitcher from './LanguageSwitcher.astro';
6
+ import { maugliConfig } from '../config/maugli.config';
7
+ import { LANGUAGES } from '../i18n/languages';
8
+
9
+ const lang = typeof maugliConfig.defaultLang === 'string' ? maugliConfig.defaultLang : 'en';
10
+ const languageObj = LANGUAGES.find((l) => l.code === lang) || LANGUAGES.find((l) => l.code === 'en');
11
+ const dict = languageObj?.dict && Object.keys(languageObj.dict).length > 0 ? languageObj.dict : LANGUAGES.find((l) => l.code === 'en')?.dict || {};
12
+
13
+ const navLinks = (maugliConfig.navLinks || []).map((link) => {
14
+ const d = dict as any;
15
+ return {
16
+ ...link,
17
+ label: d?.nav?.[link.key] || link.label
18
+ };
19
+ });
20
+ ---
21
+
22
+ <nav class="fixed top-0 left-0 w-full z-50 bg-[var(--bg-main)] opacity-95 backdrop-blur-lg flex items-center justify-between pt-4 pb-4">
23
+ <div class="flex items-center gap-8 w-full">
24
+ <a href={maugliConfig.brand.logoHref && maugliConfig.brand.logoHref.trim() ? maugliConfig.brand.logoHref : '/'} class="ml-6 flex-shrink-0 card-blur">
25
+ <picture>
26
+ <source srcset={maugliConfig.brand.logoDark} media="(prefers-color-scheme: dark)" />
27
+ <img src={maugliConfig.brand.logoLight} alt="Логотип" width="48" height="48" class="w-12 h-12" />
28
+ </picture>
29
+ </a>
30
+ <ul
31
+ id="menu-items"
32
+ class="menu flex gap-8 items-center mt-16 md:mt-0 justify-center w-full md:static md:flex-row md:gap-8 md:items-center md:justify-center max-md:flex-col max-md:gap-1 max-md:absolute max-md:top-0 max-md:right-0 max-md:left-0 max-md:w-screen max-md:pt-4 max-md:pb-10 max-md:z-20 max-md:rounded-none max-md:border-0 max-md:items-start max-md:bg-[var(--bg-main)] max-md:border-b max-md:border-b-[var(--border-main)] max-md:mx-0 max-md:p-0"
33
+ >
34
+ {
35
+ navLinks.map((link) => (
36
+ <li class="py-1 w-full text-left md:w-auto md:text-center max-md:pl-4">
37
+ <NavLink class="text-xl font-sans text-heading md:text-base" href={link.href}>
38
+ {link.label}
39
+ </NavLink>
40
+ </li>
41
+ ))
42
+ }
43
+ </ul>
44
+ </div>
45
+ <div class="flex items-center gap-2 md:gap-4 ml-auto mr-6">
46
+ <LanguageSwitcher class="order-1" />
47
+ {maugliConfig.enableThemeSwitcher !== false && <ThemeToggle class="order-2" />}
48
+ <button
49
+ class="menu-toggle cursor-pointer w-8 h-8 flex items-center justify-center relative z-30 md:hidden ml-auto text-[var(--text-heading)] order-3"
50
+ aria-expanded="false"
51
+ aria-controls="menu-items"
52
+ >
53
+ <span class="menu-toggle-icon w-6 h-px relative bg-current"></span>
54
+ </button>
55
+ </div>
56
+ </nav>
57
+
58
+ <style>
59
+ .text-heading {
60
+ color: var(--text-heading);
61
+ }
62
+ .nav-active {
63
+ color: var(--brand-color) !important;
64
+ font-weight: bold;
65
+ text-decoration: none !important;
66
+ }
67
+ @reference "tailwindcss";
68
+
69
+ @media (max-width: 767px) {
70
+ .menu {
71
+ @apply invisible opacity-0;
72
+ width: calc(100% + 1.25rem);
73
+ }
74
+ .menu.is-visible {
75
+ @apply visible opacity-100;
76
+ transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1) 0.2s;
77
+ }
78
+ .menu-toggle-icon {
79
+ transition: width 0.1s cubic-bezier(0.4, 0, 0.2, 1) 0.3s;
80
+ }
81
+ .menu-toggle.is-active .menu-toggle-icon {
82
+ @apply w-0;
83
+ transition: width 0.1s cubic-bezier(0.4, 0, 0.2, 1);
84
+ }
85
+ .menu-toggle-icon:before,
86
+ .menu-toggle-icon:after {
87
+ @apply w-6 h-px absolute left-1/2 top-0 origin-center -translate-x-1/2 bg-current;
88
+ content: '';
89
+ transition:
90
+ rotate 0.2s cubic-bezier(0.4, 0, 0.2, 1),
91
+ margin 0.2s cubic-bezier(0.4, 0, 0.2, 1) 0.2s;
92
+ }
93
+ .menu-toggle-icon:before {
94
+ @apply -mt-1.5;
95
+ }
96
+ .menu-toggle-icon:after {
97
+ @apply mt-1.5;
98
+ }
99
+ .menu-toggle.is-active .menu-toggle-icon:before,
100
+ .menu-toggle.is-active .menu-toggle-icon:after {
101
+ @apply mt-0;
102
+ transition:
103
+ margin 0.2s cubic-bezier(0.4, 0, 0.2, 1) 0.1s,
104
+ rotate 0.2s cubic-bezier(0.4, 0, 0.2, 1) 0.3s;
105
+ }
106
+ .menu-toggle.is-active .menu-toggle-icon:before {
107
+ @apply rotate-45;
108
+ }
109
+ .menu-toggle.is-active .menu-toggle-icon:after {
110
+ @apply -rotate-45;
111
+ }
112
+ }
113
+ </style>
114
+
115
+ <script>
116
+ function menuToggle() {
117
+ const menu = document.querySelector('.menu');
118
+ const menuToggleBtn = document.querySelector('.menu-toggle');
119
+ if (!menu || !menuToggleBtn) return;
120
+ menuToggleBtn.addEventListener('click', () => {
121
+ const isMenuExpanded = menuToggleBtn.getAttribute('aria-expanded') === 'true';
122
+ menuToggleBtn.classList.toggle('is-active');
123
+ menuToggleBtn.setAttribute('aria-expanded', isMenuExpanded ? 'false' : 'true');
124
+ menu.classList.toggle('is-visible');
125
+ });
126
+ // Закрытие при клике вне меню
127
+ document.addEventListener('click', (e) => {
128
+ if (!menuToggleBtn.contains(e.target as Node) && !menu.contains(e.target as Node)) {
129
+ menuToggleBtn.classList.remove('is-active');
130
+ menuToggleBtn.setAttribute('aria-expanded', 'false');
131
+ menu.classList.remove('is-visible');
132
+ }
133
+ });
134
+ // Закрытие при прокрутке
135
+ window.addEventListener('scroll', () => {
136
+ menuToggleBtn.classList.remove('is-active');
137
+ menuToggleBtn.setAttribute('aria-expanded', 'false');
138
+ menu.classList.remove('is-visible');
139
+ });
140
+ // Закрытие при выборе пункта меню
141
+ menu.querySelectorAll('li').forEach((item) => {
142
+ item.addEventListener('click', () => {
143
+ menuToggleBtn.classList.remove('is-active');
144
+ menuToggleBtn.setAttribute('aria-expanded', 'false');
145
+ menu.classList.remove('is-visible');
146
+ });
147
+ });
148
+ }
149
+ menuToggle();
150
+ document.addEventListener('astro:after-swap', menuToggle);
151
+ </script>
@@ -0,0 +1,31 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+
4
+ type Props = HTMLAttributes<'a'>;
5
+
6
+ const { href, class: className, ...props } = Astro.props;
7
+ const { pathname } = Astro.url;
8
+ const isActive = href === pathname || href === pathname.replace(/\/$/, '');
9
+ ---
10
+
11
+ <a
12
+ class:list={[
13
+ className,
14
+ isActive
15
+ ? 'text-[1.25rem] text-brand-color transition-colors duration-200 font-sans font-normal nav-link nav-active uppercase'
16
+ : 'text-[1.25rem] text-heading transition-colors duration-200 font-sans font-normal nav-link uppercase'
17
+ ]}
18
+ href={href}
19
+ {...props}
20
+ >
21
+ <slot />
22
+ </a>
23
+
24
+ <style>
25
+ .nav-link:hover,
26
+ .nav-link.nav-active {
27
+ color: var(--brand-color);
28
+ font-weight: normal;
29
+ text-transform: uppercase;
30
+ }
31
+ </style>
@@ -0,0 +1,70 @@
1
+ ---
2
+ import { maugliConfig } from '../config/maugli.config';
3
+ import { LANGUAGES } from '../i18n/languages';
4
+ import ArrowLeft from '../icons/ArrowLeft.astro';
5
+ import ArrowRight from '../icons/ArrowRight.astro';
6
+ // Универсальный импорт словарей по доступным языкам
7
+ const dicts: Record<string, any> = {};
8
+ for (const lang of LANGUAGES) {
9
+ try {
10
+ dicts[lang.code] = await import(`../i18n/${lang.code}.json`).then((m) => m.default);
11
+ } catch {}
12
+ }
13
+ const { page, class: className } = Astro.props;
14
+ const lang = maugliConfig.defaultLang || 'en';
15
+ const dict = dicts[lang] || dicts['en'] || {};
16
+
17
+ function getGoToPageLabel(targetPage, lastPage) {
18
+ return dict.pagination.goToPage.replace('{page}', targetPage).replace('{lastPage}', lastPage);
19
+ }
20
+ function getPageOfLabel(currentPage, lastPage) {
21
+ return dict.pagination.pageOf.replace('{currentPage}', currentPage).replace('{lastPage}', lastPage);
22
+ }
23
+ ---
24
+
25
+ <nav aria-label="Pagination" class:list={['flex items-center justify-center gap-4', className]}>
26
+ {
27
+ page.url.prev && (
28
+ <a href={page.url.prev} aria-label={getGoToPageLabel(page.currentPage - 1, page.lastPage)} class="pagination-btn">
29
+ <ArrowLeft class="w-5 h-5 fill-current" />
30
+ </a>
31
+ )
32
+ }
33
+ <span class="pagination-text" aria-current="page">{getPageOfLabel(page.currentPage, page.lastPage)}</span>
34
+ {
35
+ page.url.next && (
36
+ <a href={page.url.next} aria-label={getGoToPageLabel(page.currentPage + 1, page.lastPage)} class="pagination-btn">
37
+ <ArrowRight class="w-5 h-5 fill-current" />
38
+ </a>
39
+ )
40
+ }
41
+ </nav>
42
+
43
+ <style>
44
+ .pagination-btn {
45
+ display: inline-flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ padding: 8px;
49
+ color: var(--text-muted);
50
+ background: transparent;
51
+ border: none;
52
+ border-radius: 12px;
53
+ transition: all 0.2s ease;
54
+ cursor: pointer;
55
+ text-decoration: none;
56
+ }
57
+
58
+ .pagination-btn:hover {
59
+ background: var(--bg-muted);
60
+ color: var(--text-main);
61
+ transform: scale(1.05);
62
+ }
63
+
64
+ .pagination-text {
65
+ font-size: 14px;
66
+ color: var(--text-muted);
67
+ padding: 0 16px;
68
+ font-weight: 500;
69
+ }
70
+ </style>
@@ -0,0 +1,22 @@
1
+ ---
2
+ import { type CollectionEntry } from 'astro:content';
3
+ import Card from './Card.astro';
4
+
5
+ type Props = { post: CollectionEntry<'blog'>; class?: string; headingLevel?: 'h2' | 'h3' };
6
+
7
+ const { post, class: className, headingLevel = 'h2' } = Astro.props;
8
+ const { title, publishDate, excerpt, image, seo, isFeatured } = post.data;
9
+ ---
10
+
11
+ <Card
12
+ href={`/blog/${post.id}/`}
13
+ title={title}
14
+ image={image}
15
+ seo={seo}
16
+ publishDate={publishDate}
17
+ excerpt={excerpt}
18
+ headingLevel={headingLevel}
19
+ isFeatured={isFeatured}
20
+ class={className}
21
+ type="blog"
22
+ />
@@ -0,0 +1,47 @@
1
+ ---
2
+ import { maugliConfig } from '../config/maugli.config';
3
+
4
+ import { LANGUAGES } from '../i18n/languages';
5
+
6
+ // Карточка баннера продукта для боковой панели статьи/проекта
7
+ // props: product (CollectionEntry<'products'>)
8
+ const { product } = Astro.props;
9
+ const { seo, image, title, id, productLink } = product?.data || {};
10
+
11
+ // Дефолтная картинка всегда
12
+ const productID = product?.data?.productID;
13
+ let bannerImage = productID ? `/${productID}.webp` : maugliConfig.defaultProductImage;
14
+ if (!bannerImage) bannerImage = maugliConfig.defaultProductImage;
15
+ // Кнопка: если есть productLink, то ссылка, иначе неактивная
16
+ const buttonHref = productLink && productLink.trim() ? productLink : null;
17
+ const isExternal = buttonHref && /^https?:\/\//.test(buttonHref);
18
+
19
+ const lang = typeof maugliConfig.defaultLang === 'string' ? maugliConfig.defaultLang : 'en';
20
+ const languageObj = LANGUAGES.find((l) => l.code === lang) || LANGUAGES.find((l) => l.code === 'en');
21
+ const fallbackDict = LANGUAGES.find((l) => l.code === 'en')?.dict || {};
22
+ const dict = languageObj?.dict && Object.keys(languageObj.dict).length > 0 ? languageObj.dict : fallbackDict;
23
+ const buttons = (dict as any).buttons || (fallbackDict as any).buttons || {};
24
+ ---
25
+
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 src={bannerImage} alt={seo?.image?.alt || image?.alt || title || 'Product'} class="absolute inset-0 w-full h-full object-cover rounded-custom z-0" />
28
+ {
29
+ buttonHref ? (
30
+ <a
31
+ href={buttonHref}
32
+ target={isExternal ? '_blank' : undefined}
33
+ rel={isExternal ? 'noopener noreferrer' : undefined}
34
+ class="flex justify-center items-center min-w-[130px] h-12 font-extrabold text-[16px] px-4 py-3 mx-4 mb-4 transition-colors duration-200 rounded-custom bg-[var(--color-brand)] text-[var(--bg-main)] no-underline z-10"
35
+ >
36
+ {buttons.moreAboutProduct}
37
+ </a>
38
+ ) : (
39
+ <button
40
+ disabled
41
+ class="flex justify-center items-center min-w-[130px] h-12 font-extrabold text-[16px] px-1 py-3 mx-4 mb-4 transition-colors duration-200 cursor-not-allowed rounded-custom bg-[var(--bg-muted)] text-[var(--text-main)] no-underline z-10"
42
+ >
43
+ {buttons.moreAboutProduct}
44
+ </button>
45
+ )
46
+ }
47
+ </div>
@@ -0,0 +1,21 @@
1
+ ---
2
+ import { type CollectionEntry } from 'astro:content';
3
+ import Card from './Card.astro';
4
+
5
+ type Props = { product: CollectionEntry<'products'>; class?: string; headingLevel?: 'h2' | 'h3' };
6
+
7
+ const { product, class: className, headingLevel = 'h2' } = Astro.props;
8
+ const { title, description, image, seo, isFeatured } = product.data;
9
+ ---
10
+
11
+ <Card
12
+ href={`/products/${product.id}/`}
13
+ title={title}
14
+ image={image}
15
+ seo={seo}
16
+ description={description}
17
+ headingLevel={headingLevel}
18
+ isFeatured={isFeatured}
19
+ class={className}
20
+ type="product"
21
+ />
@@ -0,0 +1,21 @@
1
+ ---
2
+ import { type CollectionEntry } from 'astro:content';
3
+ import Card from './Card.astro';
4
+
5
+ type Props = { project: CollectionEntry<'projects'>; class?: string; headingLevel?: 'h2' | 'h3' };
6
+
7
+ const { project, class: className, headingLevel = 'h2' } = Astro.props;
8
+ const { title, description, image, seo, isFeatured } = project.data;
9
+ ---
10
+
11
+ <Card
12
+ href={`/projects/${project.id}/`}
13
+ title={title}
14
+ image={image}
15
+ seo={seo}
16
+ description={description}
17
+ headingLevel={headingLevel}
18
+ isFeatured={isFeatured}
19
+ class={className}
20
+ type="project"
21
+ />
@@ -0,0 +1,142 @@
1
+ ---
2
+ import { getFilteredCollection } from '../utils/content-loader';
3
+ import { maugliConfig } from '../config/maugli.config';
4
+ import { slugify } from '../utils/common-utils';
5
+ import { getPostsByTag } from '../utils/data-utils';
6
+ import CountBadge from './CountBadge.astro';
7
+
8
+ export interface Props {
9
+ rubric: {
10
+ title: string;
11
+ description?: string;
12
+ image?: { src: string; alt?: string };
13
+ quote?: string;
14
+ isFeatured?: boolean;
15
+ lang?: string;
16
+ };
17
+ class?: string;
18
+ }
19
+
20
+ const { rubric, class: className = '' } = Astro.props;
21
+ const { title, description, image, quote, isFeatured, lang } = rubric;
22
+ const defaultImage = maugliConfig.defaultRubricImage;
23
+
24
+ // Универсальный механизм локализации
25
+ import de from '../i18n/de.json';
26
+ import en from '../i18n/en.json';
27
+ import es from '../i18n/es.json';
28
+ import ru from '../i18n/ru.json';
29
+ const dicts: Record<string, any> = { ru, en, es, de };
30
+ // Если появятся новые языки — просто добавить импорт и в объект выше
31
+ const langKey =
32
+ typeof lang === 'string' && dicts[lang]
33
+ ? lang
34
+ : typeof maugliConfig.defaultLang === 'string' && dicts[maugliConfig.defaultLang]
35
+ ? maugliConfig.defaultLang
36
+ : 'en';
37
+ const dict = dicts[langKey] || dicts['en'];
38
+ const localeMap: Record<string, string> = { ru: 'ru-RU', en: 'en-US', es: 'es-ES', de: 'de-DE' };
39
+ const locale = localeMap[langKey] || 'en-US';
40
+
41
+ // Получаем slug рубрики из title через slugify
42
+ const slug = slugify(title);
43
+
44
+ // Получаем коллекцию постов и считаем количество по тегу (slug)
45
+ let postCount = 0;
46
+ let lastPublishDate = '';
47
+ try {
48
+ const posts = await getFilteredCollection('blog');
49
+ const rubricPosts = getPostsByTag(posts, slug);
50
+ postCount = rubricPosts.length;
51
+ if (postCount > 0) {
52
+ // Находим максимальный publishDate
53
+ lastPublishDate =
54
+ rubricPosts
55
+ .map((post) => post.data.publishDate)
56
+ .filter(Boolean)
57
+ .map((d) => new Date(d))
58
+ .sort((a, b) => b.getTime() - a.getTime())[0]
59
+ ?.toISOString() || '';
60
+ }
61
+ } catch (e) {
62
+ postCount = 0;
63
+ lastPublishDate = '';
64
+ }
65
+
66
+ function formatDate(dateStr?: string) {
67
+ if (!dateStr) return '';
68
+ const date = new Date(dateStr);
69
+ const now = new Date();
70
+ const isToday = date.toDateString() === now.toDateString();
71
+ const yesterday = new Date(now);
72
+ yesterday.setDate(now.getDate() - 1);
73
+ const isYesterday = date.toDateString() === yesterday.toDateString();
74
+ if (isToday) return dict.date.today;
75
+ if (isYesterday) return dict.date.yesterday;
76
+ const optionsCurrentYear = { day: '2-digit', month: 'long' };
77
+ const optionsOtherYear = { day: '2-digit', month: 'long', year: 'numeric' };
78
+ if (date.getFullYear() === now.getFullYear()) {
79
+ return date.toLocaleDateString(locale, { ...optionsCurrentYear } as any);
80
+ }
81
+ return date.toLocaleDateString(locale, { ...optionsOtherYear } as any);
82
+ }
83
+
84
+ const formattedDate = formatDate(lastPublishDate);
85
+ const dateObj = lastPublishDate ? new Date(lastPublishDate) : null;
86
+ const now = new Date();
87
+ const isToday = dateObj && dateObj.toDateString() === now.toDateString();
88
+ const yesterday = new Date(now);
89
+ yesterday.setDate(now.getDate() - 1);
90
+ const isYesterday = dateObj && dateObj.toDateString() === yesterday.toDateString();
91
+ const isBrandDate = isToday || isYesterday;
92
+ ---
93
+
94
+ <a href={`/tags/${slug}/`} class="block w-full">
95
+ <article
96
+ 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}`}
97
+ >
98
+ <!-- Левая часть: картинка и дата -->
99
+ <div class="flex flex-col items-end gap-2 w-[105px] h-[147px]">
100
+ <img
101
+ src={image?.src ? image.src : maugliConfig.defaultRubricImage}
102
+ alt={image?.alt || title}
103
+ class="w-[105px] h-[107px] object-cover rounded-custom"
104
+ loading="lazy"
105
+ />
106
+ <div class="flex flex-col items-end gap-1 w-[74px] h-[32px]">
107
+ <span class={`flex items-center gap-1 text-[12px] text-right ${isBrandDate ? 'text-[var(--brand-color)]' : 'text-[var(--text-muted)]'}`}>
108
+ <svg
109
+ width="16"
110
+ height="16"
111
+ viewBox="0 0 24 24"
112
+ fill="none"
113
+ xmlns="http://www.w3.org/2000/svg"
114
+ style="display:inline-block;vertical-align:middle;opacity:0.6; color: var(--text-muted);"
115
+ >
116
+ <path
117
+ 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"
118
+ fill="currentColor"></path>
119
+ </svg>
120
+ {formattedDate}
121
+ </span>
122
+ </div>
123
+ </div>
124
+ <!-- Правая часть: контент -->
125
+ <div class="flex flex-col justify-start items-start h-[147px] flex-1 min-w-0">
126
+ <div class="flex flex-row items-start gap-2 w-full">
127
+ <h3 class="font-serif font-[700] text-[22px] text-[var(--text-heading)] leading-[1] truncate">{title}</h3>
128
+ <CountBadge count={postCount} />
129
+ </div>
130
+ <div class="mt-2 text-[14px] text-[var(--text-main)] leading-[1.3] line-clamp-6 opacity-80">
131
+ {description}
132
+ </div>
133
+ </div>
134
+ </article>
135
+ </a>
136
+
137
+ <style>
138
+ article:hover {
139
+ box-shadow: var(--card-shadow);
140
+ }
141
+ /* Карточка всегда двухколоночная, контент сверху, текст description не прижат к низу */
142
+ </style>
@@ -0,0 +1,93 @@
1
+ ---
2
+ import { maugliConfig } from '../config/maugli.config';
3
+ import { LANGUAGES } from '../i18n/languages';
4
+ const dicts: Record<string, any> = {};
5
+ for (const lang of LANGUAGES) {
6
+ try {
7
+ dicts[lang.code] = await import(`../i18n/${lang.code}.json`).then((m) => m.default);
8
+ } catch {}
9
+ }
10
+ export interface Props {
11
+ platform: 'twitter' | 'copy' | 'telegram' | 'linkedin' | 'whatsapp' | 'facebook' | 'copied';
12
+ }
13
+
14
+ const { platform } = Astro.props;
15
+
16
+ const currentLang = maugliConfig.defaultLang || 'en';
17
+ const dict = dicts[currentLang] || dicts['en'] || {};
18
+ const ariaLabels = dict.shareIconAriaLabel || {};
19
+ const ariaLabel = ariaLabels[platform] || '';
20
+
21
+ const getIcon = (platform: string) => {
22
+ const icons = {
23
+ twitter: `<svg width="100%" height="100%" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
24
+ <path d="M11.0207 8.96875L18.7675 19.0295H17.0129L9.26554 8.96875H11.0207Z" fill="currentColor" fill-opacity="0.8"/>
25
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M14 0C21.732 0 28 6.26801 28 14C28 21.732 21.732 28 14 28C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0ZM7.61466 8.16667L12.5701 14.6021L7.58333 19.8333H8.70614L13.0709 15.2521L16.5982 19.8333H20.4167L15.1838 13.0367L19.8236 8.16667H18.7031L14.683 12.385L11.4354 8.16667H7.61466Z" fill="currentColor" fill-opacity="0.8"/>
26
+ </svg>`,
27
+
28
+ copy: `<svg width="100%" height="100%" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
29
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M14 0C21.732 0 28 6.26801 28 14C28 21.732 21.732 28 14 28C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0ZM11.3112 12.8618C11.1393 12.8618 10.9742 12.93 10.8526 13.0515L8.34611 15.5649C7.84129 16.1144 7.56815 16.8377 7.5839 17.5837C7.5997 18.3299 7.90325 19.0413 8.43099 19.569C8.95873 20.0968 9.67009 20.4003 10.4163 20.4161C11.1623 20.4319 11.8856 20.1587 12.4351 19.6539L14.9485 17.1474C15.07 17.0258 15.1382 16.8607 15.1382 16.6888C15.1382 16.5169 15.07 16.3518 14.9485 16.2302C14.8268 16.1086 14.6614 16.04 14.4893 16.04C14.3174 16.04 14.1523 16.1086 14.0308 16.2302L11.5243 18.7367C11.2208 19.0295 10.8157 19.193 10.394 19.193C9.9723 19.193 9.56673 19.0296 9.26326 18.7367C9.11447 18.5885 8.99635 18.4123 8.91577 18.2183C8.83519 18.0243 8.79386 17.816 8.79386 17.606C8.79387 17.396 8.83526 17.1881 8.91577 16.9941C8.99631 16.8002 9.11454 16.6239 9.26326 16.4757L11.7698 13.9692C11.8914 13.8476 11.96 13.6826 11.96 13.5107C11.96 13.3386 11.8914 13.1732 11.7698 13.0515C11.6482 12.93 11.4831 12.8618 11.3112 12.8618ZM15.5489 11.8022C15.3769 11.8023 15.212 11.8709 15.0903 11.9925L11.9122 15.1707C11.8517 15.2307 11.8037 15.3022 11.7709 15.3809C11.7381 15.4596 11.7208 15.544 11.7208 15.6292C11.7208 15.7145 11.7381 15.7989 11.7709 15.8776C11.8037 15.9563 11.8516 16.0278 11.9122 16.0878C11.9726 16.1477 12.0443 16.1952 12.123 16.2274C12.2016 16.2595 12.2858 16.2757 12.3708 16.2752C12.4557 16.2757 12.5399 16.2595 12.6186 16.2274C12.6973 16.1952 12.769 16.1477 12.8293 16.0878L16.0075 12.9097C16.1291 12.788 16.1977 12.6231 16.1978 12.4511C16.1978 12.2791 16.1291 12.1142 16.0075 11.9925C15.8858 11.8709 15.7209 11.8022 15.5489 11.8022ZM17.5291 7.58333C16.763 7.58333 16.0279 7.88645 15.4845 8.42643L12.9718 10.9329C12.9116 10.9931 12.8636 11.0645 12.8311 11.1431C12.7985 11.2218 12.7815 11.3064 12.7815 11.3915C12.7815 11.4767 12.7985 11.5612 12.8311 11.6399C12.8636 11.7185 12.9116 11.7899 12.9718 11.8501C13.032 11.9103 13.1033 11.9582 13.182 11.9908C13.2607 12.0234 13.3452 12.0404 13.4303 12.0404C13.5154 12.0403 13.5996 12.0233 13.6781 11.9908C13.7568 11.9582 13.8287 11.9103 13.8889 11.8501L16.3954 9.34359C16.6988 9.05085 17.104 8.88731 17.5256 8.88729C17.9474 8.88729 18.353 9.05071 18.6564 9.34359C18.8052 9.4918 18.9233 9.66803 19.0039 9.86198C19.0845 10.056 19.1258 10.2643 19.1258 10.4744C19.1258 10.6843 19.0844 10.8923 19.0039 11.0862C18.9234 11.2801 18.8052 11.4564 18.6564 11.6046L16.1499 14.1111C16.0894 14.1711 16.0414 14.2427 16.0086 14.3213C15.9759 14.3999 15.9585 14.4844 15.9585 14.5697C15.9585 14.6549 15.9759 14.7393 16.0086 14.818C16.0414 14.8967 16.0894 14.9682 16.1499 15.0282C16.21 15.0888 16.2814 15.1373 16.3601 15.1701C16.4388 15.2028 16.5233 15.2196 16.6085 15.2196C16.6937 15.2196 16.7782 15.2029 16.8569 15.1701C16.9356 15.1373 17.007 15.0888 17.0671 15.0282L19.5736 12.5155C20.1135 11.9721 20.4167 11.237 20.4167 10.4709C20.4166 9.70486 20.1136 8.96983 19.5736 8.42643C19.0302 7.88641 18.2951 7.58336 17.5291 7.58333Z" fill="currentColor" fill-opacity="0.8"/>
30
+ </svg>`,
31
+
32
+ telegram: `<svg width="100%" height="100%" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
33
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M14 0C21.732 0 28 6.26801 28 14C28 21.732 21.732 28 14 28C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0ZM19.209 8.16667C18.7423 8.17491 18.0258 8.4236 14.5799 9.85685C13.373 10.3589 10.9608 11.3981 7.34351 12.974C6.75617 13.2076 6.44818 13.4365 6.42008 13.6599C6.36633 14.0889 6.98397 14.2225 7.76107 14.4751C8.39458 14.681 9.24686 14.9218 9.68994 14.9314C10.0918 14.94 10.5409 14.7747 11.0361 14.4347C14.4155 12.1535 16.1602 11.0002 16.2701 10.9751C16.3473 10.9576 16.4545 10.9352 16.527 10.9996C16.5993 11.0639 16.5922 11.1858 16.5846 11.2189C16.5231 11.4808 13.3495 14.3679 13.1654 14.5588C12.4672 15.284 11.6731 15.7276 12.8983 16.535C13.9584 17.2336 14.5753 17.6796 15.6674 18.3955C16.3653 18.853 16.9127 19.3955 17.6333 19.3292C17.9648 19.2986 18.3076 18.9868 18.4815 18.0571C18.8926 15.8594 19.7007 11.0972 19.8875 9.13509C19.9037 8.96324 19.8829 8.7434 19.8664 8.64689C19.8498 8.55033 19.8153 8.41263 19.6898 8.31079C19.5412 8.19027 19.3117 8.16489 19.209 8.16667Z" fill="currentColor" fill-opacity="0.8"/>
34
+ </svg>`,
35
+
36
+ linkedin: `<svg width="100%" height="100%" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
37
+ <path d="M16.4889 12.0575C17.6633 12.0575 18.9321 12.7547 18.9321 14.7964L18.931 18.9321H16.8272V15.2795C16.8272 14.2022 16.369 13.8695 15.7779 13.8695C15.1538 13.8697 14.5417 14.3406 14.5417 15.3068V18.9321H12.4368V12.2449H14.4609V13.1717H14.4882C14.6914 12.7605 15.4029 12.0575 16.4889 12.0575Z" fill="currentColor" fill-opacity="0.8"/>
38
+ <path d="M11.1728 18.9264H9.06787V12.2403H11.1728V18.9264Z" fill="currentColor" fill-opacity="0.8"/>
39
+ <path d="M9.89274 8.91919C10.127 8.87322 10.3702 8.89802 10.5906 8.98983C10.8109 9.08163 10.9989 9.23671 11.1312 9.4353C11.2636 9.63401 11.3342 9.8676 11.334 10.1064C11.3362 10.2662 11.3066 10.4249 11.2463 10.5729C11.1859 10.7209 11.0959 10.855 10.9825 10.9677C10.8691 11.0803 10.7344 11.1692 10.586 11.2286C10.4376 11.288 10.2787 11.3168 10.1189 11.3135C9.88021 11.3121 9.64736 11.2403 9.44954 11.1067C9.25169 10.973 9.09731 10.7837 9.00692 10.5627C8.9166 10.3417 8.89395 10.0987 8.94141 9.86483C8.9889 9.6309 9.10476 9.41613 9.27409 9.24788C9.44338 9.07974 9.6586 8.96517 9.89274 8.91919Z" fill="currentColor" fill-opacity="0.8"/>
40
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M14 0C21.732 0 28 6.26801 28 14C28 21.732 21.732 28 14 28C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0ZM8.03337 7C7.75931 7.00003 7.49628 7.1087 7.30249 7.30249C7.1087 7.49628 7.00003 7.75931 7 8.03337V19.9666C7.00003 20.2407 7.1087 20.5037 7.30249 20.6975C7.49628 20.8913 7.75931 21 8.03337 21H19.9666C20.2407 21 20.5037 20.8913 20.6975 20.6975C20.8913 20.5037 21 20.2407 21 19.9666V8.03337C21 7.75931 20.8913 7.49628 20.6975 7.30249C20.5037 7.1087 20.2407 7.00003 19.9666 7H8.03337Z" fill="currentColor" fill-opacity="0.8"/>
41
+ </svg>`,
42
+
43
+ whatsapp: `<svg width="100%" height="100%" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
44
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M14.0302 8.17464C15.5779 8.17467 17.0352 8.7736 18.1283 9.86483C19.2214 10.9561 19.8231 12.4032 19.8231 13.9436C19.823 17.1241 17.2225 19.7091 14.0302 19.7091H14.0268C12.9871 19.7058 11.9674 19.4299 11.0782 18.9042L10.8674 18.7778L8.6748 19.3497L9.25985 17.2237L9.12256 17.0044C8.5409 16.0861 8.23673 15.0247 8.23673 13.9368C8.23675 10.7562 10.8377 8.17131 14.0302 8.17464ZM11.563 10.733C11.446 10.733 11.2586 10.7759 11.0981 10.9489C10.9377 11.1219 10.4897 11.5414 10.4897 12.3964C10.4898 13.248 11.1148 14.0731 11.2018 14.1897C11.2887 14.3061 12.4088 16.1125 14.1772 16.8079C15.648 17.3868 15.9491 17.2702 16.2667 17.2402C16.5844 17.2101 17.296 16.8211 17.443 16.4154C17.5867 16.0096 17.5867 15.6639 17.5433 15.5905C17.4998 15.5173 17.3827 15.4736 17.2089 15.3871C17.0345 15.3003 16.1793 14.8819 16.0189 14.822C15.8585 14.7655 15.7417 14.7353 15.6281 14.908C15.5111 15.081 15.1764 15.4706 15.0761 15.5871C14.9759 15.7033 14.8721 15.7168 14.6984 15.6304C14.5245 15.5439 13.9632 15.3605 13.2982 14.7685C12.7802 14.3095 12.429 13.7408 12.3286 13.5676C12.2283 13.3946 12.3186 13.3015 12.4055 13.215C12.4824 13.1385 12.5795 13.0118 12.6664 12.9119C12.7532 12.8122 12.7834 12.7389 12.8402 12.6226C12.8969 12.5062 12.8699 12.4065 12.8265 12.3201C12.783 12.2369 12.4419 11.3782 12.2882 11.0355C12.1579 10.7463 12.021 10.7397 11.8974 10.7364C11.7971 10.7331 11.68 10.733 11.563 10.733Z" fill="currentColor" fill-opacity="0.8"/>
45
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M14 0C21.732 0 28 6.26801 28 14C28 21.732 21.732 28 14 28C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0ZM14.0302 7C10.1892 7 7.0604 10.1107 7.06038 13.9368C7.05704 15.1577 7.37783 16.352 7.9895 17.4066L7 21L10.6903 20.0316C11.7098 20.5839 12.8567 20.8769 14.0234 20.877H14.0268C17.871 20.8769 21 17.7662 21 13.9402C21.0033 12.087 20.2777 10.3434 18.9606 9.03255C17.6469 7.72189 15.8954 7.00003 14.0302 7Z" fill="currentColor" fill-opacity="0.8"/>
46
+ </svg>`,
47
+
48
+ facebook: `<svg width="100%" height="100%" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
49
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M14 0C21.732 0 28 6.26801 28 14C28 21.732 21.732 28 14 28C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0ZM15.6685 7.58333C13.7987 7.58333 12.5776 8.72769 12.5776 10.7985V12.6191H10.5V15.0083H12.5776V20.7835C12.9947 20.8497 13.4217 20.8832 13.8564 20.8832C14.2911 20.8832 14.7177 20.8497 15.1348 20.7835V15.0083H17.0414L17.4043 12.6191H15.1348V11.0697C15.1348 10.416 15.4514 9.77824 16.4678 9.77824H17.5V7.74455C17.4937 7.74346 16.5605 7.58334 15.6685 7.58333Z" fill="currentColor" fill-opacity="0.8"/>
50
+ </svg>`,
51
+
52
+ copied: `<svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
53
+ <path d="M9 12L11 14L15 10M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="#4AE84F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
54
+ </svg>`
55
+ };
56
+
57
+ return icons[platform as keyof typeof icons] || '';
58
+ };
59
+ ---
60
+
61
+ <div class="share-icon" set:html={getIcon(platform)} aria-label={ariaLabel} />
62
+
63
+ <style>
64
+ .share-icon {
65
+ width: 28px;
66
+ height: 28px;
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ transition: transform 0.2s ease;
71
+ color: var(--text-main);
72
+ }
73
+
74
+ .share-icon svg {
75
+ color: inherit;
76
+ }
77
+
78
+ /* Планшеты */
79
+ @media (min-width: 768px) and (max-width: 1023px) {
80
+ .share-icon {
81
+ width: 28px;
82
+ height: 28px;
83
+ }
84
+ }
85
+
86
+ /* Десктоп */
87
+ @media (min-width: 1024px) {
88
+ .share-icon {
89
+ width: 36px;
90
+ height: 36px;
91
+ }
92
+ }
93
+ </style>